Merge "Update component id for room to internal id" into androidx-main
diff --git a/buildSrc/shared.gradle b/buildSrc/shared.gradle
index 32d2f1e..e858364 100644
--- a/buildSrc/shared.gradle
+++ b/buildSrc/shared.gradle
@@ -53,11 +53,6 @@
     implementation(project(":jetpad-integration")) // Doesn't have a .pom, so not slow to load
 }
 
-// Exclude dokka coming from AGP. We don't need it and it conflicts with dackka: b/195305339
-configurations.configureEach { conf ->
-  conf.exclude(group:"org.jetbrains.dokka", module:"dokka-core")
-}
-
 // Saves configuration into destFile
 // Each line of destFile will be the absolute filepath of one of the files in configuration
 def saveConfigurationResolution(configuration, destFile) {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
index ef7ccc3..47c4175 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
@@ -20,7 +20,6 @@
 import android.graphics.Rect
 import android.hardware.camera2.CameraCharacteristics
 import android.util.Rational
-import android.util.Size
 import androidx.annotation.RequiresApi
 import androidx.arch.core.util.Function
 import androidx.camera.camera2.pipe.CameraPipe
@@ -48,13 +47,13 @@
 import androidx.camera.core.impl.utils.futures.FutureChain
 import androidx.camera.core.impl.utils.futures.Futures
 import com.google.common.util.concurrent.ListenableFuture
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.guava.asListenableFuture
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 /**
  * Adapt the [CameraControlInternal] interface to [CameraPipe].
@@ -165,7 +164,7 @@
         return false
     }
 
-    override fun addZslConfig(resolution: Size, sessionConfigBuilder: SessionConfig.Builder) {
+    override fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder) {
         // Override if Zero-Shutter Lag needs to add config to session config.
     }
 
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
index e2d548e..aad2b48 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
@@ -796,8 +796,14 @@
     @SdkSuppress(minSdkVersion = 23)
     @Test
     public void attachUseCaseWithTemplateZSLNoRecord() throws InterruptedException {
-        UseCase preview = createUseCase(CameraDevice.TEMPLATE_PREVIEW, /* isZslDisabled = */false);
-        UseCase zsl = createUseCase(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
+        if (!mCamera2CameraImpl.getCameraInfo().isZslSupported()) {
+            return;
+        }
+        UseCase preview = createUseCase(
+                CameraDevice.TEMPLATE_PREVIEW,
+                /* isZslDisabled = */false);
+        UseCase zsl = createUseCase(
+                CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
                 /* isZslDisabled = */false);
 
         mCamera2CameraImpl.attachUseCases(Arrays.asList(preview, zsl));
@@ -815,19 +821,24 @@
 
         assertThat(captureResult.get(CaptureResult.CONTROL_CAPTURE_INTENT))
                 .isEqualTo(CaptureRequest.CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isFalse();
 
         mCamera2CameraImpl.detachUseCases(Arrays.asList(preview, zsl));
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
-                .isFalse();
+        assertThat(mCamera2CameraImpl.getCameraControlInternal()
+                .isZslDisabledByByUserCaseConfig()).isFalse();
     }
 
     @SdkSuppress(minSdkVersion = 23)
     @Test
     public void attachUseCaseWithTemplateZSLHasRecord() throws InterruptedException {
-        UseCase preview = createUseCase(CameraDevice.TEMPLATE_PREVIEW, /* isZslDisabled = */false);
+        if (!mCamera2CameraImpl.getCameraInfo().isZslSupported()) {
+            return;
+        }
+        UseCase preview = createUseCase(CameraDevice.TEMPLATE_PREVIEW, /* isZslDisabled = */
+                false);
         UseCase record = createUseCase(CameraDevice.TEMPLATE_RECORD, /* isZslDisabled = */true);
         UseCase zsl = createUseCase(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
                 /* isZslDisabled = */false);
@@ -848,19 +859,25 @@
 
         assertThat(captureResult.get(CaptureResult.CONTROL_CAPTURE_INTENT))
                 .isEqualTo(CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isTrue();
 
         mCamera2CameraImpl.detachUseCases(Arrays.asList(preview, record, zsl));
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isFalse();
     }
 
     @SdkSuppress(minSdkVersion = 23)
     @Test
     public void attachAndDetachUseCasesMultipleTimes() throws InterruptedException {
-        UseCase preview = createUseCase(CameraDevice.TEMPLATE_PREVIEW, /* isZslDisabled = */false);
+        if (!mCamera2CameraImpl.getCameraInfo().isZslSupported()) {
+            return;
+        }
+        UseCase preview = createUseCase(CameraDevice.TEMPLATE_PREVIEW, /* isZslDisabled = */
+                false);
         UseCase record = createUseCase(CameraDevice.TEMPLATE_RECORD, /* isZslDisabled = */true);
         UseCase zsl = createUseCase(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
                 /* isZslDisabled = */false);
@@ -870,40 +887,47 @@
         mCamera2CameraImpl.onUseCaseActive(zsl);
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
 
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isFalse();
 
         mCamera2CameraImpl.attachUseCases(Arrays.asList(record));
         mCamera2CameraImpl.onUseCaseActive(record);
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
 
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isTrue();
 
         mCamera2CameraImpl.detachUseCases(Arrays.asList(record));
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isFalse();
 
         mCamera2CameraImpl.attachUseCases(Arrays.asList(record));
         mCamera2CameraImpl.onUseCaseActive(record);
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isTrue();
 
         mCamera2CameraImpl.detachUseCases(Arrays.asList(zsl));
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isTrue();
 
         mCamera2CameraImpl.detachUseCases(Arrays.asList(preview));
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isTrue();
 
         mCamera2CameraImpl.detachUseCases(Arrays.asList(record));
         HandlerUtil.waitForLooperToIdle(sCameraHandler);
-        assertThat(mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
+        assertThat(
+                mCamera2CameraImpl.getCameraControlInternal().isZslDisabledByByUserCaseConfig())
                 .isFalse();
     }
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java
index fb33c76..d504a72 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java
@@ -30,7 +30,6 @@
 import android.os.Build;
 import android.util.ArrayMap;
 import android.util.Rational;
-import android.util.Size;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
@@ -390,9 +389,8 @@
     }
 
     @Override
-    public void addZslConfig(@NonNull Size resolution,
-            @NonNull SessionConfig.Builder sessionConfigBuilder) {
-        mZslControl.addZslConfig(resolution, sessionConfigBuilder);
+    public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
+        mZslControl.addZslConfig(sessionConfigBuilder);
     }
 
     @Override
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java
index 5d78d06..c8550d6 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java
@@ -17,7 +17,6 @@
 package androidx.camera.camera2.internal;
 
 import android.media.ImageWriter;
-import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -32,12 +31,9 @@
     /**
      * Adds zero-shutter lag config to {@link SessionConfig}.
      *
-     * @param resolution surface resolution.
      * @param sessionConfigBuilder session config builder.
      */
-    void addZslConfig(
-            @NonNull Size resolution,
-            @NonNull SessionConfig.Builder sessionConfigBuilder);
+    void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder);
 
     /**
      * Sets the flag if zero-shutter lag needs to be disabled by user case config.
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java
index 2216bd41..12ecb98 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java
@@ -16,14 +16,16 @@
 
 package androidx.camera.camera2.internal;
 
+import static android.graphics.ImageFormat.PRIVATE;
 import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
-import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING;
 
 import static androidx.camera.camera2.internal.ZslUtil.isCapabilitySupported;
 
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -45,10 +47,14 @@
 import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.ImmediateSurface;
 import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.impl.utils.CompareSizesByArea;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.internal.compat.ImageWriterCompat;
 import androidx.camera.core.internal.utils.ZslRingBuffer;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.NoSuchElementException;
 
 /**
@@ -65,6 +71,12 @@
     @VisibleForTesting
     static final int MAX_IMAGES = RING_BUFFER_CAPACITY * 3;
 
+    @NonNull
+    private final Map<Integer, Size> mReprocessingInputSizeMap;
+
+    @NonNull
+    private final CameraCharacteristicsCompat mCameraCharacteristicsCompat;
+
     @VisibleForTesting
     @SuppressWarnings("WeakerAccess")
     @NonNull
@@ -72,7 +84,6 @@
 
     private boolean mIsZslDisabledByUseCaseConfig = false;
     private boolean mIsZslDisabledByFlashMode = false;
-    private boolean mIsYuvReprocessingSupported = false;
     private boolean mIsPrivateReprocessingSupported = false;
 
     @SuppressWarnings("WeakerAccess")
@@ -84,13 +95,13 @@
     ImageWriter mReprocessingImageWriter;
 
     ZslControlImpl(@NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat) {
-        mIsYuvReprocessingSupported =
-                isCapabilitySupported(cameraCharacteristicsCompat,
-                        REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
+        mCameraCharacteristicsCompat = cameraCharacteristicsCompat;
         mIsPrivateReprocessingSupported =
-                isCapabilitySupported(cameraCharacteristicsCompat,
+                isCapabilitySupported(mCameraCharacteristicsCompat,
                         REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
 
+        mReprocessingInputSizeMap = createReprocessingInputSizeMap(mCameraCharacteristicsCompat);
+
         mImageRingBuffer = new ZslRingBuffer(
                 RING_BUFFER_CAPACITY,
                 imageProxy -> imageProxy.close());
@@ -117,9 +128,7 @@
     }
 
     @Override
-    public void addZslConfig(
-            @NonNull Size resolution,
-            @NonNull SessionConfig.Builder sessionConfigBuilder) {
+    public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
         cleanup();
 
         // Early return only if use case config doesn't support zsl. If flash mode doesn't
@@ -130,15 +139,17 @@
             return;
         }
 
-        if (!mIsYuvReprocessingSupported && !mIsPrivateReprocessingSupported) {
+        // Due to b/232268355 and feedback from pixel team that private format will have better
+        // performance, we will use private only for zsl.
+        if (!mIsPrivateReprocessingSupported
+                || mReprocessingInputSizeMap.isEmpty()
+                || !mReprocessingInputSizeMap.containsKey(PRIVATE)
+                || !isJpegValidOutputForInputFormat(mCameraCharacteristicsCompat, PRIVATE)) {
             return;
         }
 
-        // Init the reprocessing image reader and enqueue available images into the ring buffer.
-        // TODO(b/226683183): Decide whether YUV or PRIVATE reprocessing should be the default.
-        int reprocessingImageFormat = mIsPrivateReprocessingSupported
-                ? ImageFormat.PRIVATE : ImageFormat.YUV_420_888;
-
+        int reprocessingImageFormat = PRIVATE;
+        Size resolution = mReprocessingInputSizeMap.get(reprocessingImageFormat);
         MetadataImageReader metadataImageReader = new MetadataImageReader(
                 resolution.getWidth(),
                 resolution.getHeight(),
@@ -258,4 +269,53 @@
             mReprocessingImageWriter = null;
         }
     }
+
+    @NonNull
+    private Map<Integer, Size> createReprocessingInputSizeMap(
+            @NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat) {
+        StreamConfigurationMap map =
+                cameraCharacteristicsCompat.get(
+                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        if (map == null || map.getInputFormats() == null) {
+            return new HashMap<>();
+        }
+
+        Map<Integer, Size> inputSizeMap = new HashMap<>();
+        for (int format: map.getInputFormats()) {
+            Size[] inputSizes = map.getInputSizes(format);
+            if (inputSizes != null) {
+                // Sort by descending order
+                Arrays.sort(inputSizes, new CompareSizesByArea(true));
+
+                // TODO(b/233696144): Check if selecting an input size closer to output size will
+                //  improve performance or not.
+                inputSizeMap.put(format, inputSizes[0]);
+            }
+        }
+        return inputSizeMap;
+    }
+
+    private boolean isJpegValidOutputForInputFormat(
+            @NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat,
+            int inputFormat) {
+        StreamConfigurationMap map =
+                cameraCharacteristicsCompat.get(
+                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        if (map == null) {
+            return false;
+        }
+
+        int[] validOutputFormats = map.getValidOutputFormatsForInput(inputFormat);
+        if (validOutputFormats == null) {
+            return false;
+        }
+        for (int outputFormat : validOutputFormats) {
+            if (outputFormat == ImageFormat.JPEG) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java
index 715e70a..9ac407e 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java
@@ -16,8 +16,6 @@
 
 package androidx.camera.camera2.internal;
 
-import android.util.Size;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.camera.core.ImageProxy;
@@ -28,8 +26,7 @@
  */
 public class ZslControlNoOpImpl implements ZslControl {
     @Override
-    public void addZslConfig(@NonNull Size resolution,
-            @NonNull SessionConfig.Builder sessionConfigBuilder) {
+    public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
     }
 
     @Override
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZslControlImplTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZslControlImplTest.kt
index 699662a..33ddaf5 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZslControlImplTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZslControlImplTest.kt
@@ -16,10 +16,12 @@
 
 package androidx.camera.camera2.internal
 
+import android.graphics.ImageFormat.JPEG
 import android.graphics.ImageFormat.PRIVATE
 import android.graphics.ImageFormat.YUV_420_888
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.params.StreamConfigurationMap
 import android.os.Build
 import android.util.Size
 import androidx.camera.camera2.internal.ZslControlImpl.MAX_IMAGES
@@ -30,13 +32,16 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 import org.robolectric.shadow.api.Shadow
 import org.robolectric.shadows.ShadowCameraCharacteristics
 
-private val RESOLUTION = Size(640, 480)
+val YUV_REPROCESSING_MAXIMUM_SIZE = Size(4000, 3000)
+val PRIVATE_REPROCESSING_MAXIMUM_SIZE = Size(3000, 2000)
 
 /**
  * Unit tests for [ZslControlImpl].
@@ -57,64 +62,81 @@
     }
 
     @Test
-    public fun isYuvReprocessingSupported_addZslConfig() {
-        zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
-            hasCapabilities = true,
-            isYuvReprocessingSupported = true,
-            isPrivateReprocessingSupported = false
-        ))
-
-        zslControl.addZslConfig(RESOLUTION, sessionConfigBuilder)
-
-        assertThat(zslControl.mReprocessingImageReader).isNotNull()
-        assertThat(zslControl.mReprocessingImageReader.imageFormat).isEqualTo(YUV_420_888)
-        assertThat(zslControl.mReprocessingImageReader.maxImages).isEqualTo(
-            MAX_IMAGES)
-        assertThat(zslControl.mImageRingBuffer.maxCapacity).isEqualTo(
-            RING_BUFFER_CAPACITY)
-    }
-
-    @Test
     public fun isPrivateReprocessingSupported_addZslConfig() {
         zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
             hasCapabilities = true,
             isYuvReprocessingSupported = false,
-            isPrivateReprocessingSupported = true
+            isPrivateReprocessingSupported = true,
+            isJpegValidOutputFormat = true
         ))
 
-        zslControl.addZslConfig(RESOLUTION, sessionConfigBuilder)
+        zslControl.addZslConfig(sessionConfigBuilder)
 
         assertThat(zslControl.mReprocessingImageReader).isNotNull()
         assertThat(zslControl.mReprocessingImageReader.imageFormat).isEqualTo(PRIVATE)
         assertThat(zslControl.mReprocessingImageReader.maxImages).isEqualTo(
             MAX_IMAGES)
+        assertThat(zslControl.mReprocessingImageReader.width).isEqualTo(
+            PRIVATE_REPROCESSING_MAXIMUM_SIZE.width)
+        assertThat(zslControl.mReprocessingImageReader.height).isEqualTo(
+            PRIVATE_REPROCESSING_MAXIMUM_SIZE.height)
         assertThat(zslControl.mImageRingBuffer.maxCapacity).isEqualTo(
             RING_BUFFER_CAPACITY)
     }
 
     @Test
+    public fun isYuvReprocessingSupported_notAddZslConfig() {
+        zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
+            hasCapabilities = true,
+            isYuvReprocessingSupported = true,
+            isPrivateReprocessingSupported = false,
+            isJpegValidOutputFormat = true
+        ))
+
+        zslControl.addZslConfig(sessionConfigBuilder)
+
+        assertThat(zslControl.mReprocessingImageReader).isNull()
+    }
+
+    @Test
+    public fun isJpegNotValidOutputFormat_notAddZslConfig() {
+        zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
+            hasCapabilities = true,
+            isYuvReprocessingSupported = true,
+            isPrivateReprocessingSupported = false,
+            isJpegValidOutputFormat = false
+        ))
+
+        zslControl.addZslConfig(sessionConfigBuilder)
+
+        assertThat(zslControl.mReprocessingImageReader).isNull()
+    }
+
+    @Test
     public fun isReprocessingNotSupported_notAddZslConfig() {
         zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
             hasCapabilities = true,
             isYuvReprocessingSupported = false,
-            isPrivateReprocessingSupported = false
+            isPrivateReprocessingSupported = false,
+            isJpegValidOutputFormat = false
         ))
 
-        zslControl.addZslConfig(RESOLUTION, sessionConfigBuilder)
+        zslControl.addZslConfig(sessionConfigBuilder)
 
         assertThat(zslControl.mReprocessingImageReader).isNull()
     }
 
     @Test
-    public fun isZslDisabledByUserCaseConfig_NotAddZslConfig() {
+    public fun isZslDisabledByUserCaseConfig_notAddZslConfig() {
         zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
             hasCapabilities = true,
             isYuvReprocessingSupported = false,
-            isPrivateReprocessingSupported = true
+            isPrivateReprocessingSupported = true,
+            isJpegValidOutputFormat = true
         ))
         zslControl.isZslDisabledByUserCaseConfig = true
 
-        zslControl.addZslConfig(RESOLUTION, sessionConfigBuilder)
+        zslControl.addZslConfig(sessionConfigBuilder)
 
         assertThat(zslControl.mReprocessingImageReader).isNull()
     }
@@ -124,16 +146,21 @@
         zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
             hasCapabilities = true,
             isYuvReprocessingSupported = false,
-            isPrivateReprocessingSupported = true
+            isPrivateReprocessingSupported = true,
+            isJpegValidOutputFormat = true
         ))
         zslControl.isZslDisabledByFlashMode = true
 
-        zslControl.addZslConfig(RESOLUTION, sessionConfigBuilder)
+        zslControl.addZslConfig(sessionConfigBuilder)
 
         assertThat(zslControl.mReprocessingImageReader).isNotNull()
         assertThat(zslControl.mReprocessingImageReader.imageFormat).isEqualTo(PRIVATE)
         assertThat(zslControl.mReprocessingImageReader.maxImages).isEqualTo(
             MAX_IMAGES)
+        assertThat(zslControl.mReprocessingImageReader.width).isEqualTo(
+            PRIVATE_REPROCESSING_MAXIMUM_SIZE.width)
+        assertThat(zslControl.mReprocessingImageReader.height).isEqualTo(
+            PRIVATE_REPROCESSING_MAXIMUM_SIZE.height)
         assertThat(zslControl.mImageRingBuffer.maxCapacity).isEqualTo(
             RING_BUFFER_CAPACITY)
     }
@@ -143,13 +170,14 @@
         zslControl = ZslControlImpl(createCameraCharacteristicsCompat(
             hasCapabilities = true,
             isYuvReprocessingSupported = false,
-            isPrivateReprocessingSupported = true
+            isPrivateReprocessingSupported = true,
+            isJpegValidOutputFormat = true
         ))
 
-        zslControl.addZslConfig(RESOLUTION, sessionConfigBuilder)
+        zslControl.addZslConfig(sessionConfigBuilder)
 
         zslControl.isZslDisabledByUserCaseConfig = true
-        zslControl.addZslConfig(RESOLUTION, sessionConfigBuilder)
+        zslControl.addZslConfig(sessionConfigBuilder)
 
         assertThat(zslControl.mReprocessingImageReader).isNull()
     }
@@ -157,7 +185,8 @@
     private fun createCameraCharacteristicsCompat(
         hasCapabilities: Boolean,
         isYuvReprocessingSupported: Boolean,
-        isPrivateReprocessingSupported: Boolean
+        isPrivateReprocessingSupported: Boolean,
+        isJpegValidOutputFormat: Boolean
     ): CameraCharacteristicsCompat {
         val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
         val shadowCharacteristics = Shadow.extract<ShadowCameraCharacteristics>(characteristics)
@@ -177,6 +206,47 @@
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES,
                 capabilities.toIntArray()
             )
+
+            // Input formats
+            val streamConfigurationMap: StreamConfigurationMap = mock(
+                StreamConfigurationMap::class.java)
+
+            if (isYuvReprocessingSupported && isPrivateReprocessingSupported) {
+                Mockito.`when`(streamConfigurationMap.inputFormats).thenReturn(
+                    arrayOf(YUV_420_888, PRIVATE).toIntArray()
+                )
+                Mockito.`when`(streamConfigurationMap.getInputSizes(YUV_420_888)).thenReturn(
+                    arrayOf(YUV_REPROCESSING_MAXIMUM_SIZE)
+                )
+                Mockito.`when`(streamConfigurationMap.getInputSizes(PRIVATE)).thenReturn(
+                    arrayOf(PRIVATE_REPROCESSING_MAXIMUM_SIZE)
+                )
+            } else if (isYuvReprocessingSupported) {
+                Mockito.`when`(streamConfigurationMap.inputFormats).thenReturn(
+                    arrayOf(YUV_420_888).toIntArray()
+                )
+                Mockito.`when`(streamConfigurationMap.getInputSizes(YUV_420_888)).thenReturn(
+                    arrayOf(YUV_REPROCESSING_MAXIMUM_SIZE)
+                )
+            } else if (isPrivateReprocessingSupported) {
+                Mockito.`when`(streamConfigurationMap.inputFormats).thenReturn(
+                    arrayOf(PRIVATE).toIntArray()
+                )
+                Mockito.`when`(streamConfigurationMap.getInputSizes(PRIVATE)).thenReturn(
+                    arrayOf(PRIVATE_REPROCESSING_MAXIMUM_SIZE)
+                )
+            }
+
+            // Output formats for input
+            if (isJpegValidOutputFormat) {
+                Mockito.`when`(streamConfigurationMap.getValidOutputFormatsForInput(PRIVATE))
+                    .thenReturn(arrayOf(JPEG).toIntArray())
+                Mockito.`when`(streamConfigurationMap.getValidOutputFormatsForInput(YUV_420_888))
+                    .thenReturn(arrayOf(JPEG).toIntArray())
+            }
+
+            shadowCharacteristics.set(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, streamConfigurationMap)
         }
 
         return CameraCharacteristicsCompat.toCameraCharacteristicsCompat(characteristics)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index ba945df..c24f28d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -375,7 +375,7 @@
         YuvToJpegProcessor softwareJpegProcessor = null;
 
         if (Build.VERSION.SDK_INT >= 23 && getCaptureMode() == CAPTURE_MODE_ZERO_SHUTTER_LAG) {
-            getCameraControl().addZslConfig(resolution, sessionConfigBuilder);
+            getCameraControl().addZslConfig(sessionConfigBuilder);
         }
 
         // Setup the ImageReader to do processing
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
index 36c1f4d..33688e9 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
@@ -19,7 +19,6 @@
 import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
 
 import android.graphics.Rect;
-import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
@@ -60,12 +59,9 @@
 
     /**
      * Adds zero-shutter lag config to {@link SessionConfig}.
-     * @param resolution surface resolution.
      * @param sessionConfigBuilder session config builder.
      */
-    void addZslConfig(
-            @NonNull Size resolution,
-            @NonNull SessionConfig.Builder sessionConfigBuilder);
+    void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder);
 
     /**
      * Sets the flag if zero-shutter lag needs to be disabled by user case config.
@@ -161,8 +157,7 @@
         }
 
         @Override
-        public void addZslConfig(@NonNull Size resolution,
-                @NonNull SessionConfig.Builder sessionConfigBuilder) {
+        public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
         }
 
         @NonNull
diff --git a/camera/camera-effects/api/current.txt b/camera/camera-effects/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/camera/camera-effects/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/camera/camera-effects/api/public_plus_experimental_current.txt b/camera/camera-effects/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/camera/camera-effects/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/camera/camera-effects/api/res-current.txt b/camera/camera-effects/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-effects/api/res-current.txt
diff --git a/camera/camera-effects/api/restricted_current.txt b/camera/camera-effects/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/camera/camera-effects/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/camera/camera-effects/build.gradle b/camera/camera-effects/build.gradle
new file mode 100644
index 0000000..e82c7eb
--- /dev/null
+++ b/camera/camera-effects/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import androidx.build.Publish
+import androidx.build.RunApiTasks
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+dependencies {
+    api(project(":camera:camera-core"))
+}
+android {
+    defaultConfig {
+        multiDexEnabled = true
+    }
+    testOptions.unitTests.includeAndroidResources = true
+}
+androidx {
+    name = "Jetpack Camera Effects Library"
+    publish = Publish.NONE
+    mavenGroup = LibraryGroups.CAMERA
+    inceptionYear = "2022"
+    runApiTasks = new RunApiTasks.Yes()
+    description = "Camera effects components for the Jetpack Camera Library, a library providing " +
+            "camera post-processing features such as portrait mode that can be used with the " +
+            "CameraX library."
+}
diff --git a/camera/camera-effects/src/main/AndroidManifest.xml b/camera/camera-effects/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f0be73e
--- /dev/null
+++ b/camera/camera-effects/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest package="androidx.camera.effects"/>
\ No newline at end of file
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
new file mode 100644
index 0000000..0284490
--- /dev/null
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.effects;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Provides a portrait post-processing effect.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class Portrait {
+    // TODO: implement this
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
index 6330c4b..0d2a5cb 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
@@ -141,8 +141,7 @@
     }
 
     @Override
-    public void addZslConfig(@NonNull Size resolution,
-            @NonNull SessionConfig.Builder sessionConfigBuilder) {
+    public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
         // Override if Zero-Shutter Lag needs to add config to session config.
         mIsZslConfigAdded = true;
     }
diff --git a/camera/integration-tests/diagnosetestapp/build.gradle b/camera/integration-tests/diagnosetestapp/build.gradle
new file mode 100644
index 0000000..181ce2a
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/build.gradle
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.application")
+    id("kotlin-android")
+    id("AndroidXComposePlugin")
+}
+
+android {
+    defaultConfig {
+        applicationId "androidx.camera.integration.diagnose"
+        minSdkVersion 21
+        multiDexEnabled true
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+        }
+    }
+    namespace "androidx.camera.integration.diagnose"
+}
+
+dependencies {
+    implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
+
+    // Internal library
+    implementation(project(":camera:camera-camera2"))
+    implementation(project(":camera:camera-view"))
+    implementation(project(":camera:camera-lifecycle"))
+
+    // Android Support Library
+    implementation("androidx.appcompat:appcompat:1.3.0")
+    implementation("androidx.activity:activity-ktx:1.2.0")
+
+    implementation(libs.guavaAndroid)
+    implementation(libs.constraintLayout)
+    implementation 'androidx.camera:camera-view:1.1.0-beta03'
+
+    compileOnly(libs.kotlinCompiler)
+
+    // Testing framework
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.testUiautomator)
+    androidTestImplementation(libs.espressoCore)
+    androidTestImplementation(libs.truth)
+}
diff --git a/camera/integration-tests/diagnosetestapp/src/main/AndroidManifest.xml b/camera/integration-tests/diagnosetestapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d612196
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.camera.integration.diagnose">>
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+
+    <application
+        android:icon="@drawable/ic_baseline_camera_enhance_24"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/camera/integration-tests/diagnosetestapp/src/main/java/androidx/camera/integration/diagnose/MainActivity.kt b/camera/integration-tests/diagnosetestapp/src/main/java/androidx/camera/integration/diagnose/MainActivity.kt
new file mode 100644
index 0000000..be73f4b
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/src/main/java/androidx/camera/integration/diagnose/MainActivity.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.diagnose
+
+import android.os.Bundle
+import android.widget.Button
+import androidx.appcompat.app.AppCompatActivity
+import androidx.camera.view.LifecycleCameraController
+import androidx.camera.view.PreviewView
+
+class MainActivity : AppCompatActivity() {
+
+    private lateinit var cameraController: LifecycleCameraController
+    private lateinit var previewView: PreviewView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        // TODO: request CAMERA permission and fail gracefully if not granted.
+
+        // Setup CameraX
+        previewView = findViewById(R.id.preview_view)
+        cameraController = LifecycleCameraController(this)
+        cameraController.bindToLifecycle(this)
+        previewView.controller = cameraController
+
+        // Setup UI events
+        findViewById<Button>(R.id.capture).setOnClickListener {
+            // TODO: handle capture button click event following examples
+            //  in CameraControllerFragment.
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/diagnosetestapp/src/main/res/drawable/ic_baseline_camera_enhance_24.xml b/camera/integration-tests/diagnosetestapp/src/main/res/drawable/ic_baseline_camera_enhance_24.xml
new file mode 100644
index 0000000..4e12ff4
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/src/main/res/drawable/ic_baseline_camera_enhance_24.xml
@@ -0,0 +1,6 @@
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M9,3L7.17,5L4,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2h-3.17L15,3L9,3zM12,18c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M12,17l1.25,-2.75L16,13l-2.75,-1.25L12,9l-1.25,2.75L8,13l2.75,1.25z"/>
+</vector>
diff --git a/camera/integration-tests/diagnosetestapp/src/main/res/layout/activity_main.xml b/camera/integration-tests/diagnosetestapp/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d9f6d47
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/src/main/res/layout/activity_main.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <FrameLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+        <androidx.camera.view.PreviewView
+            android:id="@+id/preview_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </FrameLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <Button
+            android:id="@+id/capture"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/capture"
+            android:checked="true"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/camera/integration-tests/diagnosetestapp/src/main/res/values/colors.xml b/camera/integration-tests/diagnosetestapp/src/main/res/values/colors.xml
new file mode 100644
index 0000000..558e3dc
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/src/main/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+</resources>
\ No newline at end of file
diff --git a/camera/integration-tests/diagnosetestapp/src/main/res/values/strings.xml b/camera/integration-tests/diagnosetestapp/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7f32560
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/src/main/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="app_name">diagnosetestapp</string>
+    <string name="capture">capture</string>
+</resources>
\ No newline at end of file
diff --git a/camera/integration-tests/diagnosetestapp/src/main/res/values/themes.xml b/camera/integration-tests/diagnosetestapp/src/main/res/values/themes.xml
new file mode 100644
index 0000000..1240763
--- /dev/null
+++ b/camera/integration-tests/diagnosetestapp/src/main/res/values/themes.xml
@@ -0,0 +1,19 @@
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar" />
+</resources>
\ No newline at end of file
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/TaskRestrictionDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/TaskRestrictionDemoScreen.java
index bc8ca3a..c3d6c17 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/TaskRestrictionDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/TaskRestrictionDemoScreen.java
@@ -136,7 +136,7 @@
                                         new CarIcon.Builder(
                                                 IconCompat.createWithResource(
                                                         getCarContext(),
-                                                        R.drawable.ic_fastfood_white_48dp))
+                                                        R.drawable.ic_fastfood_yellow_48dp))
                                                 .build(),
                                         mImageType)
                                 .setOnClickListener(
diff --git a/car/app/app-samples/showcase/common/src/main/res/drawable/ic_fastfood_yellow_48dp.xml b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_fastfood_yellow_48dp.xml
new file mode 100644
index 0000000..cbce92b
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_fastfood_yellow_48dp.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M36.12,45.98h3.32c1.69,0 3.07,-1.27 3.26,-2.92L46,10.1L36,10.1L36,2h-3.94v8.1h-9.94l0.6,4.68c3.43,0.94 6.63,2.64 8.53,4.52 2.88,2.85 4.87,5.77 4.87,10.59L36.12,46v-0.02zM2,43.98L2,42h30.06v1.98c0,1.1 -0.9,2 -2.02,2L4.02,45.98c-1.12,0 -2.02,-0.91 -2.02,-2zM32.06,29.98c0,-16 -30.06,-16 -30.06,0h30.06zM2.04,34h30v4h-30z"
+      android:fillColor="#FFFF00"/>
+</vector>
diff --git a/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt
index 5f7031d..1ad4377 100644
--- a/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt
+++ b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt
@@ -137,8 +137,9 @@
     get() = WindowInsetsHolder.current().displayCutout
 
 /**
- * For the [WindowInsetsCompat.Type.ime]. On [Build.VERSION_CODES.R] and above, the
- * soft keyboard can be detected and [ime] will animate when it shows.
+ * For the [WindowInsetsCompat.Type.ime]. On API level 23 (M) and above, the soft keyboard can be
+ * detected and [ime] will update when it shows. On API 30 (R) and above, the [ime] insets will
+ * animate synchronously with the actual IME animation.
  *
  * Developers should set `android:windowSoftInputMode="adjustResize"` in their
  * `AndroidManifest.xml` file and call `WindowCompat.setDecorFitsSystemWindows(window, false)`
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index 04d445d..0edb218 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -65,5 +65,6 @@
         ComposableDemo("Custom Touch Slop Demo - Composition Locals") { CustomTouchSlopSample() },
         ComposableDemo("Focused bounds") { FocusedBoundsDemo() },
         ComposableDemo("Scrollable with focused child") { ScrollableFocusedChildDemo() },
+        ComposableDemo("Window insets") { WindowInsetsDemo() },
     )
 )
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/WindowInsetsDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/WindowInsetsDemo.kt
new file mode 100644
index 0000000..22000fb
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/WindowInsetsDemo.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment.Companion.CenterHorizontally
+import androidx.compose.ui.Alignment.Companion.CenterVertically
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun WindowInsetsDemo() {
+    val insets = WindowInsets.safeDrawing
+    val density = LocalDensity.current
+
+    Column {
+        Text(
+            "The numbers around the text field below show the respective WindowInsets values for" +
+                " the safeDrawing insets. To use this demo, go the demo app settings (⚙️ icon), " +
+                "set the soft input mode to AdjustResize, and disable Decor Fits System Windows." +
+                " If you don't configure the settings this way, the insets will not be updated." +
+                " Note that IME insets are only supported on API 23 and above."
+        )
+
+        BasicTextField(
+            value = "Click to show keyboard",
+            onValueChange = {},
+            modifier = Modifier
+                .fillMaxSize()
+                .wrapContentSize(),
+            textStyle = TextStyle(color = Color.Black.copy(alpha = 0.5f))
+        ) { field ->
+            with(density) {
+                Column(horizontalAlignment = CenterHorizontally) {
+                    Text(
+                        insets.getTop(density).toDp().toString(),
+                        style = MaterialTheme.typography.caption
+                    )
+                    Row(verticalAlignment = CenterVertically) {
+                        val layoutDirection = LocalLayoutDirection.current
+                        Text(
+                            insets.getLeft(density, layoutDirection).toDp().toString(),
+                            style = MaterialTheme.typography.caption
+                        )
+                        Box(
+                            Modifier
+                                .padding(2.dp)
+                                .border(1.dp, Color.Black)
+                        ) {
+                            field()
+                        }
+                        Text(
+                            insets.getRight(density, layoutDirection).toDp().toString(),
+                            style = MaterialTheme.typography.caption
+                        )
+                    }
+                    Text(
+                        insets.getBottom(density).toDp().toString(),
+                        style = MaterialTheme.typography.caption
+                    )
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
index c66bd05..6644869 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
@@ -412,6 +412,41 @@
     }
 
     @Test
+    fun verticalGrid_largeContentPaddingAndReverseLayout() {
+        val topPadding = itemSize * 2
+        val bottomPadding = itemSize * 2
+        val listSize = itemSize * 3
+        lateinit var state: LazyGridState
+        rule.setContentWithTestViewConfiguration {
+            LazyVerticalGrid(
+                GridCells.Fixed(1),
+                reverseLayout = true,
+                state = rememberLazyGridState().also { state = it },
+                modifier = Modifier.size(listSize),
+                contentPadding = PaddingValues(top = topPadding, bottom = bottomPadding),
+            ) {
+                items(3) { index ->
+                    Box(Modifier.size(itemSize).testTag("$index"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertTopPositionInRootIsEqualTo(0.dp)
+        // Not visible.
+        rule.onNodeWithTag("1")
+            .assertIsNotDisplayed()
+
+        // Scroll to the top.
+        state.scrollBy(itemSize * 5f)
+
+        rule.onNodeWithTag("2").assertTopPositionInRootIsEqualTo(topPadding)
+        // Shouldn't be visible
+        rule.onNodeWithTag("1").assertIsNotDisplayed()
+        rule.onNodeWithTag("0").assertIsNotDisplayed()
+    }
+
+    @Test
     fun column_overscrollWithContentPadding() {
         lateinit var state: LazyGridState
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
index 98054cb..29c3a644 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
@@ -386,6 +386,42 @@
     }
 
     @Test
+    fun contentLargePaddingAndReverseLayout() {
+        val topPadding = itemSize * 2
+        val bottomPadding = itemSize * 2
+        val listSize = itemSize * 3
+        lateinit var state: LazyListState
+        rule.setContentWithTestViewConfiguration {
+            LazyColumnOrRow(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(listSize),
+                contentPadding = PaddingValues(
+                    beforeContent = topPadding,
+                    afterContent = bottomPadding
+                ),
+            ) {
+                items(3) { index ->
+                    Box(Modifier.requiredSize(itemSize).testTag("$index"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertStartPositionInRootIsEqualTo(0.dp)
+        // Shouldn't be visible
+        rule.onNodeWithTag("1").assertIsNotDisplayed()
+
+        // Scroll to the top.
+        state.scrollBy(itemSize * 5f)
+
+        rule.onNodeWithTag("2").assertStartPositionInRootIsEqualTo(topPadding)
+        // Shouldn't be visible
+        rule.onNodeWithTag("1").assertIsNotDisplayed()
+        rule.onNodeWithTag("0").assertIsNotDisplayed()
+    }
+
+    @Test
     fun overscrollWithContentPadding() {
         lateinit var state: LazyListState
         rule.setContent {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
index 788671a..0b6ea3e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
@@ -31,6 +32,7 @@
 import androidx.compose.ui.platform.debugInspectorInfo
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
 
 /**
  * Configure component to be hoverable via pointer enter/exit events.
@@ -51,6 +53,7 @@
         properties["enabled"] = enabled
     }
 ) {
+    val scope = rememberCoroutineScope()
     var hoverInteraction by remember { mutableStateOf<HoverInteraction.Enter?>(null) }
 
     suspend fun emitEnter() {
@@ -96,11 +99,13 @@
 //  LocalPointerPosition.current
             .pointerInput(interactionSource) {
                 val currentContext = currentCoroutineContext()
-                while (currentContext.isActive) {
-                    val event = awaitPointerEventScope { awaitPointerEvent() }
-                    when (event.type) {
-                        PointerEventType.Enter -> emitEnter()
-                        PointerEventType.Exit -> emitExit()
+                awaitPointerEventScope {
+                    while (currentContext.isActive) {
+                        val event = awaitPointerEvent()
+                        when (event.type) {
+                            PointerEventType.Enter -> scope.launch { emitEnter() }
+                            PointerEventType.Exit -> scope.launch { emitExit() }
+                        }
                     }
                 }
             }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 8b20471..4f0750c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -231,6 +231,24 @@
 
         val itemsCount = itemProvider.itemCount
 
+        // can be negative if the content padding is larger than the max size from constraints
+        val mainAxisAvailableSize = if (isVertical) {
+            containerConstraints.maxHeight - totalVerticalPadding
+        } else {
+            containerConstraints.maxWidth - totalHorizontalPadding
+        }
+        val visualItemOffset = if (!reverseLayout || mainAxisAvailableSize > 0) {
+            IntOffset(startPadding, topPadding)
+        } else {
+            // When layout is reversed and paddings together take >100% of the available space,
+            // layout size is coerced to 0 when positioning. To take that space into account,
+            // we offset start padding by negative space between paddings.
+            IntOffset(
+                if (isVertical) startPadding else startPadding + mainAxisAvailableSize,
+                if (isVertical) topPadding + mainAxisAvailableSize else topPadding
+            )
+        }
+
         val measuredItemProvider = LazyMeasuredItemProvider(
             contentConstraints,
             isVertical,
@@ -251,20 +269,13 @@
                 beforeContentPadding = beforeContentPadding,
                 afterContentPadding = afterContentPadding,
                 spacing = spacing,
-                visualOffset = IntOffset(startPadding, topPadding),
+                visualOffset = visualItemOffset,
                 key = key,
                 placementAnimator = placementAnimator
             )
         }
         state.premeasureConstraints = measuredItemProvider.childConstraints
 
-        // can be negative if the content padding is larger than the max size from constraints
-        val mainAxisAvailableSize = if (isVertical) {
-            containerConstraints.maxHeight - totalVerticalPadding
-        } else {
-            containerConstraints.maxWidth - totalHorizontalPadding
-        }
-
         val firstVisibleItemIndex: DataIndex
         val firstVisibleScrollOffset: Int
         Snapshot.withoutReadObservation {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
index c7989d0..448fe9d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
@@ -231,6 +231,24 @@
 
         val itemsCount = itemProvider.itemCount
 
+        // can be negative if the content padding is larger than the max size from constraints
+        val mainAxisAvailableSize = if (isVertical) {
+            constraints.maxHeight - totalVerticalPadding
+        } else {
+            constraints.maxWidth - totalHorizontalPadding
+        }
+        val visualItemOffset = if (!reverseLayout || mainAxisAvailableSize > 0) {
+            IntOffset(startPadding, topPadding)
+        } else {
+            // When layout is reversed and paddings together take >100% of the available space,
+            // layout size is coerced to 0 when positioning. To take that space into account,
+            // we offset start padding by negative space between paddings.
+            IntOffset(
+                if (isVertical) startPadding else startPadding + mainAxisAvailableSize,
+                if (isVertical) topPadding + mainAxisAvailableSize else topPadding
+            )
+        }
+
         val measuredItemProvider = LazyMeasuredItemProvider(
             itemProvider,
             this,
@@ -246,7 +264,7 @@
                 layoutDirection = layoutDirection,
                 beforeContentPadding = beforeContentPadding,
                 afterContentPadding = afterContentPadding,
-                visualOffset = IntOffset(startPadding, topPadding),
+                visualOffset = visualItemOffset,
                 placeables = placeables,
                 placementAnimator = placementAnimator
             )
@@ -285,13 +303,6 @@
             result
         }
 
-        // can be negative if the content padding is larger than the max size from constraints
-        val mainAxisAvailableSize = if (isVertical) {
-            constraints.maxHeight - totalVerticalPadding
-        } else {
-            constraints.maxWidth - totalHorizontalPadding
-        }
-
         val firstVisibleLineIndex: LineIndex
         val firstVisibleLineScrollOffset: Int
 
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
index ddf3d1f..5ed26fc 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -38,6 +38,7 @@
 import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.graphics.painter.ColorPainter
 import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.semantics
@@ -62,6 +63,7 @@
 import androidx.compose.ui.test.swipeRight
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.height
 import androidx.compose.ui.unit.width
@@ -343,6 +345,29 @@
         assertThat(textStyle).isEqualTo(expectedTextStyle)
     }
 
+    @Test
+    fun centerAlignedTopAppBar_measureWithNonZeroMinWidth() {
+        var appBarSize = IntSize.Zero
+        rule.setMaterialContent(lightColorScheme()) {
+            CenterAlignedTopAppBar(
+                modifier = Modifier.layout { measurable, constraints ->
+                    val placeable = measurable.measure(
+                        constraints.copy(minWidth = constraints.maxWidth)
+                    )
+                    appBarSize = IntSize(placeable.width, placeable.height)
+                    layout(placeable.width, placeable.height) {
+                        placeable.place(0, 0)
+                    }
+                },
+                title = {
+                    Text("Title")
+                }
+            )
+        }
+
+        assertThat(appBarSize).isNotEqualTo(IntSize.Zero)
+    }
+
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun centerAlignedTopAppBar_contentColor() {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index fd41db4..afab12b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -66,6 +66,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
@@ -1002,16 +1003,22 @@
         modifier = modifier
     ) { measurables, constraints ->
         val navigationIconPlaceable =
-            measurables.first { it.layoutId == "navigationIcon" }.measure(constraints)
+            measurables.first { it.layoutId == "navigationIcon" }
+                .measure(constraints.copy(minWidth = 0))
         val actionIconsPlaceable =
-            measurables.first { it.layoutId == "actionIcons" }.measure(constraints)
+            measurables.first { it.layoutId == "actionIcons" }
+                .measure(constraints.copy(minWidth = 0))
 
-        val maxTitleWidth =
-            constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width
+        val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) {
+            constraints.maxWidth
+        } else {
+            (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
+                .coerceAtLeast(0)
+        }
         val titlePlaceable =
-            measurables
-                .first { it.layoutId == "title" }
-                .measure(constraints.copy(maxWidth = maxTitleWidth))
+            measurables.first { it.layoutId == "title" }
+                .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
+
         // Locate the title's baseline.
         val titleBaseline =
             if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index fcb4815..8ce5bfd 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -1027,9 +1027,10 @@
 
 /**
  * The result of a applying a mutable snapshot. [Success] indicates that the snapshot was
- * successfully applied and is not visible as the global state of the state object (or visible
+ * successfully applied and is now visible as the global state of the state object (or visible
  * in the parent snapshot for a nested snapshot). [Failure] indicates one or more state objects
- * were modified by both this snapshot and in the global (or parent) snapshot.
+ * were modified by both this snapshot and in the global (or parent) snapshot, and the changes from
+ * this snapshot are **not** visible in the global or parent snapshot.
  */
 sealed class SnapshotApplyResult {
     /**
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt
index 2d5bf83..780227e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifierTest.kt
@@ -17,46 +17,22 @@
 package androidx.compose.ui.input.nestedscroll
 
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.background
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.AbstractComposeView
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.action.CoordinatesProvider
-import androidx.test.espresso.action.GeneralLocation
-import androidx.test.espresso.action.GeneralSwipeAction
-import androidx.test.espresso.action.Press
-import androidx.test.espresso.action.Swipe
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import kotlin.math.abs
-import kotlin.math.sign
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.runBlocking
-import org.hamcrest.CoreMatchers.allOf
-import org.hamcrest.CoreMatchers.instanceOf
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -985,133 +961,7 @@
         }
     }
 
-    @Test
-    fun nestedScroll_movingTarget_velocityShouldRespectSign() {
-        var lastVelocity = Velocity.Zero
-        val MaxOffsetBound = 900f
-        val ConsumedEverything = 0.0f
-        rule.setContent {
-            var offset by remember {
-                mutableStateOf(MaxOffsetBound)
-            }
-            val nestedScrollConnection = remember {
-                object : NestedScrollConnection {
-                    fun consumedDelta(scrollDelta: Float): Float {
-                        if (offset == 0f && scrollDelta < 0f) return ConsumedEverything
-                        if (offset == MaxOffsetBound && scrollDelta > 0f) return ConsumedEverything
-                        val previousOffset = offset
-                        offset = (scrollDelta + offset).coerceIn(0f, MaxOffsetBound)
-                        return offset - previousOffset
-                    }
-
-                    override fun onPreScroll(
-                        available: Offset,
-                        source: NestedScrollSource
-                    ): Offset {
-                        return if (available.y < 0) {
-                            val consumed = consumedDelta(available.y)
-                            Offset(x = 0f, y = consumed)
-                        } else Offset.Zero
-                    }
-
-                    override fun onPostScroll(
-                        consumed: Offset,
-                        available: Offset,
-                        source: NestedScrollSource
-                    ): Offset {
-                        return if (abs(available.y) > 0f &&
-                            available.y > 0f
-                        ) {
-                            Offset(0f, consumedDelta(available.y))
-                        } else
-                            super.onPostScroll(consumed, available, source)
-                    }
-
-                    override suspend fun onPreFling(available: Velocity): Velocity {
-                        lastVelocity = available
-                        return super.onPreFling(available)
-                    }
-                }
-            }
-
-            Column(modifier = Modifier.fillMaxSize()) {
-                Box(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .height(80.dp)
-                )
-                LazyColumn(
-                    modifier = Modifier
-                        .graphicsLayer {
-                            translationY = offset
-                        }
-                        .nestedScroll(connection = nestedScrollConnection)
-                        .fillMaxWidth()
-                        .weight(1f)
-                ) {
-                    items(100) {
-                        Box(
-                            modifier = Modifier
-                                .fillMaxWidth()
-                                .height(60.dp)
-                                .background(Color.Gray)
-                        ) {
-                            BasicText(text = it.toString())
-                        }
-                        Spacer(modifier = Modifier.height(8.dp))
-                    }
-                }
-            }
-        }
-
-        composeViewSwipeUp()
-        rule.runOnIdle {
-            // swipe ups provide negative signed velocities
-            assertThat(sign(lastVelocity.y)).isEqualTo(-1)
-        }
-        composeViewSwipeDown()
-        rule.runOnIdle {
-            // swipe downs provide positive signed velocities
-            assertThat(sign(lastVelocity.y)).isEqualTo(1)
-        }
-        composeViewSwipeDown()
-        rule.runOnIdle {
-            // swipe downs provide positive signed velocities
-            assertThat(sign(lastVelocity.y)).isEqualTo(1)
-        }
-    }
-
-// helper functions
-
-    private fun composeViewSwipeUp() {
-        onView(allOf(instanceOf(AbstractComposeView::class.java)))
-            .perform(
-                espressoSwipe(
-                    GeneralLocation.BOTTOM_CENTER,
-                    GeneralLocation.CENTER
-                )
-            )
-    }
-
-    private fun composeViewSwipeDown() {
-        onView(allOf(instanceOf(AbstractComposeView::class.java)))
-            .perform(
-                espressoSwipe(
-                    GeneralLocation.CENTER,
-                    GeneralLocation.BOTTOM_CENTER
-                )
-            )
-    }
-
-    private fun espressoSwipe(
-        start: CoordinatesProvider,
-        end: CoordinatesProvider
-    ): GeneralSwipeAction {
-        return GeneralSwipeAction(
-            Swipe.FAST, start, end,
-            Press.FINGER
-        )
-    }
+    // helper functions
 
     private fun testMiddleParentAdditionRemoval(
         content: @Composable (root: Modifier, middle: Modifier, child: Modifier) -> Unit
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index b6d3da5..580a3b8d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -167,7 +167,6 @@
     private val samples: Array<PointAtTime?> = Array(HistorySize) { null }
     private var index: Int = 0
     private val useImpulse = true
-    internal var currentPositionDeltaAccumulator = Offset.Zero
 
     /**
      * Adds a position as the given time to the tracker.
@@ -246,7 +245,6 @@
      */
     fun resetTracking() {
         samples.fill(element = null)
-        currentPositionDeltaAccumulator = Offset.Zero
     }
 
     /**
@@ -341,13 +339,9 @@
 fun VelocityTracker.addPointerInputChange(event: PointerInputChange) {
     @OptIn(ExperimentalComposeUiApi::class)
     event.historical.fastForEach {
-        val historicalDelta = event.position - it.position
-        currentPositionDeltaAccumulator += historicalDelta
-        addPosition(it.uptimeMillis, currentPositionDeltaAccumulator)
+        addPosition(it.uptimeMillis, it.position)
     }
-    val delta = event.position - event.previousPosition
-    currentPositionDeltaAccumulator += delta
-    addPosition(event.uptimeMillis, currentPositionDeltaAccumulator)
+    addPosition(event.uptimeMillis, event.position)
 }
 
 private data class PointAtTime(val point: Offset, val time: Long)
@@ -355,7 +349,7 @@
 /**
  * A two dimensional velocity estimate.
  *
- * VelocityEstimates are computed by [VelocityTracker.getLsq2VelocityEstimate]. An
+ * VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An
  * estimate's [confidence] measures how well the velocity tracker's position
  * data fit a straight line, [durationMillis] is the time that elapsed between the
  * first and last position sample used to compute the velocity, and [offset]
diff --git a/core/core-i18n/build.gradle b/core/core-i18n/build.gradle
index 9af09f9..970de8e 100644
--- a/core/core-i18n/build.gradle
+++ b/core/core-i18n/build.gradle
@@ -30,6 +30,7 @@
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.testRunner)
+    androidTestImplementation(project(":core:core"))
 }
 
 androidx {
diff --git a/core/core-i18n/src/androidTest/java/androidx/core/i18n/DateTimeFormatterTest.kt b/core/core-i18n/src/androidTest/java/androidx/core/i18n/DateTimeFormatterTest.kt
index 6ded3bb..acacedd 100644
--- a/core/core-i18n/src/androidTest/java/androidx/core/i18n/DateTimeFormatterTest.kt
+++ b/core/core-i18n/src/androidTest/java/androidx/core/i18n/DateTimeFormatterTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import android.util.Log
+import androidx.core.os.BuildCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
@@ -46,9 +47,11 @@
         private const val AVAILABLE_PERIOD_B = Build.VERSION_CODES.Q
     }
 
-    // To make the if (version > ?) a bit more readable
+    /** Starting with Android N ICU4J is public API. */
     private val isIcuAvailable = Build.VERSION.SDK_INT >= AVAILABLE_ICU4J
+    /** Starting with Android S ICU honors the "-u-hc-" extension in locale id. */
     private val isHcExtensionHonored = Build.VERSION.SDK_INT >= AVAILABLE_HC_U_EXT
+    /** Starting with Android Q ICU supports "b" and "B". */
     private val isFlexiblePeriodAvailable = Build.VERSION.SDK_INT >= AVAILABLE_PERIOD_B
 
     private val logTag = this::class.qualifiedName
@@ -279,7 +282,10 @@
         } else {
             "12:43 AM || 4:43 AM || 8:43 AM || 12:43 PM || 4:43 PM || 8:43 PM"
         }
-        val expectedZh = if (isFlexiblePeriodAvailable) {
+        val expectedZh = if (BuildCompat.isAtLeastT()) {
+            // Chinese changed to 24h from ICU 70.1
+            "00:43 || 04:43 || 08:43 || 12:43 || 16:43 || 20:43"
+        } else if (isFlexiblePeriodAvailable) {
             "凌晨12:43 || 凌晨4:43 || 上午8:43 || 中午12:43 || 下午4:43 || 晚上8:43"
         } else {
             "上午12:43 || 上午4:43 || 上午8:43 || 下午12:43 || 下午4:43 || 下午8:43"
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index d09a27d..684c9866 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -643,4 +643,5 @@
 Setting the namespace via a source AndroidManifest\.xml's package attribute is deprecated\.
 Please instead set the namespace \(or testNamespace\) in the module's build\.gradle file, as described here: https://developer\.android\.com/studio/build/configure\-app\-module\#set\-namespace
 This migration can be done automatically using the AGP Upgrade Assistant, please refer to https://developer\.android\.com/studio/build/agp\-upgrade\-assistant for more information\.
-
+# > Task :room:integration-tests:room-testapp:mergeDexWithExpandProjectionDebugAndroidTest b/233674378
+WARNING:D8: Application does not contain `androidx\.tracing\.Trace` as referenced in main-dex-list\.
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index ab2be5a..99cfc43 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -36,6 +36,7 @@
     docs(project(":browser:browser"))
     docs(project(":camera:camera-camera2"))
     docs(project(":camera:camera-core"))
+    docs(project(":camera:camera-effects"))
     docs(project(":camera:camera-extensions"))
     stubs(fileTree(dir: "../camera/camera-extensions-stub", include: ["camera-extensions-stub.jar"]))
     docs(project(":camera:camera-mlkit-vision"))
diff --git a/media2/OWNERS b/media2/OWNERS
index afe4bcd..e629564 100644
--- a/media2/OWNERS
+++ b/media2/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 461042
+# Bug component: 188488
 andrewlewis@google.com
 gyumin@google.com
 hdmoon@google.com
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 76b7067..de61639 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -40,6 +40,13 @@
     buildFeatures {
         aidl = true
     }
+    buildTypes {
+        debug {
+            // Need to make sure androidx.tracing.Trace gets put in primary dex for legacy multidex
+            // as it is needed by androidx.test.runner.AndroidJUnitRunner
+            multiDexKeepFile file('multidex-config.txt')
+        }
+    }
     sourceSets {
         androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
         androidTest.assets.srcDirs += files("$projectDir/databases".toString())
diff --git a/room/integration-tests/testapp/multidex-config.txt b/room/integration-tests/testapp/multidex-config.txt
new file mode 100644
index 0000000..c108ccb
--- /dev/null
+++ b/room/integration-tests/testapp/multidex-config.txt
@@ -0,0 +1 @@
+androidx/tracing/Trace.class
diff --git a/settings.gradle b/settings.gradle
index e648143..363b8e6 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -351,6 +351,7 @@
 includeProject(":camera:camera-camera2-pipe-integration", [BuildType.MAIN])
 includeProject(":camera:camera-camera2-pipe-testing", [BuildType.MAIN])
 includeProject(":camera:camera-core", [BuildType.MAIN])
+includeProject(":camera:camera-effects", [BuildType.MAIN])
 includeProject(":camera:camera-extensions", [BuildType.MAIN])
 includeProject(":camera:camera-extensions-stub", [BuildType.MAIN])
 includeProject(":camera:camera-lifecycle", [BuildType.MAIN])
@@ -361,6 +362,7 @@
 includeProject(":camera:camera-view", [BuildType.MAIN])
 includeProject(":camera:integration-tests:camera-testapp-camera2-pipe", "camera/integration-tests/camerapipetestapp", [BuildType.MAIN])
 includeProject(":camera:integration-tests:camera-testapp-core", "camera/integration-tests/coretestapp", [BuildType.MAIN])
+includeProject(":camera:integration-tests:camera-testapp-diagnose", "camera/integration-tests/diagnosetestapp", [BuildType.MAIN])
 includeProject(":camera:integration-tests:camera-testapp-extensions", "camera/integration-tests/extensionstestapp", [BuildType.MAIN])
 includeProject(":camera:integration-tests:camera-testapp-viewfinder", "camera/integration-tests/viewfindertestapp", [BuildType.MAIN])
 includeProject(":camera:integration-tests:camera-testapp-timing", "camera/integration-tests/timingtestapp", [BuildType.MAIN])
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/InvalidationTest.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/InvalidationTest.kt
index 2d1f45e..5238b46 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/InvalidationTest.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/InvalidationTest.kt
@@ -48,7 +48,7 @@
     val temporaryFolder = TemporaryFolder(getInstrumentation().context.cacheDir)
 
     @Test
-    @FlakyTest(bugId = 159202455)
+    @Ignore // b/159202455
     fun test_exec_hook_methods() = test_simple_hook_methods(
         listOf(
             "execute()V",
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index d3855f7..de42dca 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -471,7 +471,10 @@
      *
      * If an androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition metadata tag is defined
      * for your WatchFaceService 's manifest, and your XML includes <ComplicationSlot> tags then you
-     * must override this method.
+     * must override this method. An exception will be thrown if the implementation returns null.
+     *
+     * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the
+     * [UserStyleSchema] returned by [createUserStyleSchema].
      */
     @Suppress("DEPRECATION")
     @WorkerThread