[go: nahoru, domu]

Merge "Add support for weights to ArcLayout" into androidx-main
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXActivityTestExtensions.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXActivityTestExtensions.kt
index 7f594e7..4bab0a9 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXActivityTestExtensions.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXActivityTestExtensions.kt
@@ -16,6 +16,7 @@
 
 package androidx.camera.integration.core
 
+import androidx.camera.integration.core.util.StressTestUtil.VIDEO_CAPTURE_AUTO_STOP_LENGTH_MS
 import androidx.test.core.app.ActivityScenario
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.IdlingRegistry
@@ -23,12 +24,18 @@
 import androidx.test.espresso.assertion.ViewAssertions
 import androidx.test.espresso.matcher.ViewMatchers
 import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
 
 /**
  * Waits until the viewfinder has received frames and its idling resource has become idle.
  */
 internal fun ActivityScenario<CameraXActivity>.waitForViewfinderIdle() {
-    val idlingResource = withActivity { viewIdlingResource }
+    val idlingResource = withActivity {
+        // Make sure that the test target use case is not null
+        assertThat(preview).isNotNull()
+        resetViewIdlingResource()
+        viewIdlingResource
+    }
     try {
         IdlingRegistry.getInstance().register(idlingResource)
         // Check the activity launched and Preview displays frames.
@@ -38,12 +45,33 @@
         IdlingRegistry.getInstance().unregister(idlingResource)
     }
 }
+/**
+ * Waits until the viewfinder has received frames and its idling resource has become idle.
+ */
+internal fun ActivityScenario<CameraXActivity>.switchCameraAndWaitForViewfinderIdle() {
+    val idlingResource = withActivity {
+        // Make sure that the test target use case is not null
+        assertThat(preview).isNotNull()
+        resetViewIdlingResource()
+        viewIdlingResource
+    }
+    try {
+        IdlingRegistry.getInstance().register(idlingResource)
+        Espresso.onView(ViewMatchers.withId(R.id.direction_toggle)).perform(click())
+    } finally { // Always release the idling resource, in case of timeout exceptions.
+        IdlingRegistry.getInstance().unregister(idlingResource)
+    }
+}
 
 /**
  * Waits until an image has been saved and its idling resource has become idle.
  */
 internal fun ActivityScenario<CameraXActivity>.takePictureAndWaitForImageSavedIdle() {
-    val idlingResource = withActivity { imageSavedIdlingResource }
+    val idlingResource = withActivity {
+        // Make sure that the test target use case is not null
+        assertThat(imageCapture).isNotNull()
+        imageSavedIdlingResource
+    }
     try {
         IdlingRegistry.getInstance().register(idlingResource)
         // Perform click to take a picture.
@@ -52,4 +80,45 @@
         IdlingRegistry.getInstance().unregister(idlingResource)
         withActivity { deleteSessionImages() }
     }
+}
+
+/**
+ * Waits until the imageAnalysis has received the required number of images and its idling resource
+ * has become idle.
+ */
+internal fun ActivityScenario<CameraXActivity>.waitForImageAnalysisIdle() {
+    val idlingResource = withActivity {
+        // Make sure that the test target use case is not null
+        assertThat(imageAnalysis).isNotNull()
+        resetAnalysisIdlingResource()
+        analysisIdlingResource
+    }
+    try {
+        IdlingRegistry.getInstance().register(idlingResource)
+        // Check the activity launched and the image analysis info is displayed on the text view.
+        Espresso.onView(ViewMatchers.withId(R.id.textView))
+            .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+    } finally { // Always release the idling resource, in case of timeout exceptions.
+        IdlingRegistry.getInstance().unregister(idlingResource)
+    }
+}
+
+/**
+ * Waits until a video has been saved and its idling resource has become idle.
+ */
+internal fun ActivityScenario<CameraXActivity>.recordVideoAndWaitForVideoSavedIdle() {
+    val idlingResource = withActivity {
+        // Make sure that the test target use case is not null
+        assertThat(videoCapture).isNotNull()
+        setVideoCaptureAutoStopLength(VIDEO_CAPTURE_AUTO_STOP_LENGTH_MS)
+        videoSavedIdlingResource
+    }
+    try {
+        IdlingRegistry.getInstance().register(idlingResource)
+        // Perform click to record a video.
+        Espresso.onView(ViewMatchers.withId(R.id.Video)).perform(click())
+    } finally { // Always release the idling resource, in case of timeout exceptions.
+        IdlingRegistry.getInstance().unregister(idlingResource)
+        withActivity { deleteSessionVideos() }
+    }
 }
\ No newline at end of file
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/LifecycleStatusChangeStressTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/LifecycleStatusChangeStressTest.kt
new file mode 100644
index 0000000..8460658
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/LifecycleStatusChangeStressTest.kt
@@ -0,0 +1,560 @@
+/*
+ * 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.core
+
+import android.Manifest
+import android.content.Context
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraSelector
+import androidx.camera.integration.core.CameraXActivity.BIND_IMAGE_ANALYSIS
+import androidx.camera.integration.core.CameraXActivity.BIND_IMAGE_CAPTURE
+import androidx.camera.integration.core.CameraXActivity.BIND_PREVIEW
+import androidx.camera.integration.core.CameraXActivity.BIND_VIDEO_CAPTURE
+import androidx.camera.integration.core.util.StressTestUtil.HOME_TIMEOUT_MS
+import androidx.camera.integration.core.util.StressTestUtil.STRESS_TEST_OPERATION_REPEAT_COUNT
+import androidx.camera.integration.core.util.StressTestUtil.STRESS_TEST_REPEAT_COUNT
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_IMAGE_ANALYSIS
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_IMAGE_CAPTURE
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_PREVIEW
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_VIDEO_CAPTURE
+import androidx.camera.integration.core.util.StressTestUtil.assumeCameraSupportUseCaseCombination
+import androidx.camera.integration.core.util.StressTestUtil.createCameraSelectorById
+import androidx.camera.integration.core.util.StressTestUtil.launchCameraXActivityAndWaitForPreviewReady
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.CoreAppTestUtil
+import androidx.camera.testing.LabTestRule
+import androidx.camera.testing.StressTestRule
+import androidx.camera.testing.fakes.FakeLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.GrantPermissionRule
+import androidx.test.uiautomator.UiDevice
+import androidx.testutils.RepeatRule
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = 21)
+class LifecycleStatusChangeStressTest(
+    private val cameraId: String
+) {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    @get:Rule
+    val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+    )
+
+    @get:Rule
+    val permissionRule: GrantPermissionRule =
+        GrantPermissionRule.grant(
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.RECORD_AUDIO
+        )
+
+    @get:Rule
+    val labTest: LabTestRule = LabTestRule()
+
+    @get:Rule
+    val repeatRule = RepeatRule()
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+
+    private lateinit var cameraProvider: ProcessCameraProvider
+    private lateinit var camera: Camera
+    private lateinit var cameraIdCameraSelector: CameraSelector
+
+    companion object {
+        @ClassRule
+        @JvmField val stressTest = StressTestRule()
+
+        @JvmStatic
+        @get:Parameterized.Parameters(name = "cameraId = {0}")
+        val parameters: Collection<String>
+            get() = CameraUtil.getBackwardCompatibleCameraIdListOrThrow()
+    }
+
+    @Before
+    fun setup(): Unit = runBlocking {
+        assumeTrue(CameraUtil.deviceHasCamera())
+        CoreAppTestUtil.assumeCompatibleDevice()
+        // Clear the device UI and check if there is no dialog or lock screen on the top of the
+        // window before start the test.
+        CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
+        // Use the natural orientation throughout these tests to ensure the activity isn't
+        // recreated unexpectedly. This will also freeze the sensors until
+        // mDevice.unfreezeRotation() in the tearDown() method. Any simulated rotations will be
+        // explicitly initiated from within the test.
+        device.setOrientationNatural()
+
+        cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
+
+        cameraIdCameraSelector = createCameraSelectorById(cameraId)
+
+        camera = withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(FakeLifecycleOwner(), cameraIdCameraSelector)
+        }
+    }
+
+    @After
+    fun tearDown(): Unit = runBlocking {
+        if (::cameraProvider.isInitialized) {
+            withContext(Dispatchers.Main) {
+                cameraProvider.unbindAll()
+                cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+            }
+        }
+
+        // Unfreeze rotation so the device can choose the orientation via its own policy. Be nice
+        // to other tests :)
+        device.unfreezeRotation()
+        device.pressHome()
+        device.waitForIdle(HOME_TIMEOUT_MS)
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkPreviewInEachTime_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkImageCaptureInEachTime_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkImagePreviewInEachTime_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkImageCaptureInEachTime_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkImageAnalysisInEachTime_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkPreviewInEachTime_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkVideoCaptureInEachTime_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkPreviewInEachTime_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkVideoCaptureInEachTime_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkImageCaptureInEachTime_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkPreviewInEachTime_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkVideoCaptureInEachTime_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun pauseResumeActivity_checkImageAnalysisInEachTime_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivity_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    /**
+     * Repeatedly pause, resume the activity and checks the use cases' capture functions can work.
+     */
+    private fun pauseResumeActivity_checkOutput_repeatedly(
+        cameraId: String,
+        useCaseCombination: Int,
+        verificationTarget: Int,
+        repeatCount: Int = STRESS_TEST_OPERATION_REPEAT_COUNT
+    ) {
+        // Launches CameraXActivity and wait for the preview ready.
+        val activityScenario =
+            launchCameraXActivityAndWaitForPreviewReady(cameraId, useCaseCombination)
+
+        // Pauses, resumes the activity, and then checks the test target use case can capture
+        // images successfully.
+        with(activityScenario) {
+            use {
+                for (i in 1..repeatCount) {
+                    // Go through pause/resume then check again for view to get frames then idle.
+                    moveToState(Lifecycle.State.CREATED)
+                    moveToState(Lifecycle.State.RESUMED)
+
+                    // Checks Preview can receive frames if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_PREVIEW) != 0) {
+                        waitForViewfinderIdle()
+                    }
+
+                    // Checks ImageCapture can take a picture if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_CAPTURE) != 0) {
+                        takePictureAndWaitForImageSavedIdle()
+                    }
+
+                    // Checks VideoCapture can record a video if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_VIDEO_CAPTURE) != 0) {
+                        recordVideoAndWaitForVideoSavedIdle()
+                    }
+
+                    // Checks ImageAnalysis can receive frames if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_ANALYSIS) != 0) {
+                        waitForImageAnalysisIdle()
+                    }
+                }
+            }
+        }
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterPauseResumeRepeatedly_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageCapture_afterPauseResumeRepeatedly_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterPauseResumeRepeatedly_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageCapture_afterPauseResumeRepeatedly_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageAnalysis_afterPauseResumeRepeatedly_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterPauseResumeRepeatedly_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkVideoCapture_afterPauseResumeRepeatedly_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterPauseResumeRepeatedly_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkVideoCapture_afterPauseResumeRepeatedly_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageCapture_afterPauseResumeRepeatedly_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterPauseResumeRepeatedly_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkVideoCapture_afterPauseResumeRepeatedly_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageAnalysis_afterPauseResumeRepeatedly_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+        pauseResumeActivityRepeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    /**
+     * Pause and resume the activity repeatedly, and then checks the use cases' capture functions
+     * can work.
+     */
+    private fun pauseResumeActivityRepeatedly_thenCheckOutput(
+        cameraId: String,
+        useCaseCombination: Int,
+        verificationTarget: Int,
+        repeatCount: Int = STRESS_TEST_OPERATION_REPEAT_COUNT
+    ) {
+        // Launches CameraXActivity and wait for the preview ready.
+        val activityScenario =
+            launchCameraXActivityAndWaitForPreviewReady(cameraId, useCaseCombination)
+
+        // Pauses, resumes the activity repleatedly, and then checks the test target use case can
+        // capture images successfully.
+        with(activityScenario) {
+            use {
+                for (i in 1..repeatCount) {
+                    moveToState(Lifecycle.State.CREATED)
+                    moveToState(Lifecycle.State.RESUMED)
+                }
+
+                // Checks Preview can receive frames if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_PREVIEW) != 0) {
+                    waitForViewfinderIdle()
+                }
+
+                // Checks ImageCapture can take a picture if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_CAPTURE) != 0) {
+                    takePictureAndWaitForImageSavedIdle()
+                }
+
+                // Checks VideoCapture can record a video if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_VIDEO_CAPTURE) != 0) {
+                    recordVideoAndWaitForVideoSavedIdle()
+                }
+
+                // Checks ImageAnalysis can receive frames if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_ANALYSIS) != 0) {
+                    waitForImageAnalysisIdle()
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/SwitchCameraStressTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/SwitchCameraStressTest.kt
new file mode 100644
index 0000000..b985799
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/SwitchCameraStressTest.kt
@@ -0,0 +1,585 @@
+/*
+ * 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.core
+
+import android.Manifest
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraSelector
+import androidx.camera.integration.core.CameraXActivity.BIND_IMAGE_ANALYSIS
+import androidx.camera.integration.core.CameraXActivity.BIND_IMAGE_CAPTURE
+import androidx.camera.integration.core.CameraXActivity.BIND_PREVIEW
+import androidx.camera.integration.core.CameraXActivity.BIND_VIDEO_CAPTURE
+import androidx.camera.integration.core.util.StressTestUtil.HOME_TIMEOUT_MS
+import androidx.camera.integration.core.util.StressTestUtil.STRESS_TEST_OPERATION_REPEAT_COUNT
+import androidx.camera.integration.core.util.StressTestUtil.STRESS_TEST_REPEAT_COUNT
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_IMAGE_ANALYSIS
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_IMAGE_CAPTURE
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_PREVIEW
+import androidx.camera.integration.core.util.StressTestUtil.VERIFICATION_TARGET_VIDEO_CAPTURE
+import androidx.camera.integration.core.util.StressTestUtil.assumeCameraSupportUseCaseCombination
+import androidx.camera.integration.core.util.StressTestUtil.createCameraSelectorById
+import androidx.camera.integration.core.util.StressTestUtil.launchCameraXActivityAndWaitForPreviewReady
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.CoreAppTestUtil
+import androidx.camera.testing.LabTestRule
+import androidx.camera.testing.StressTestRule
+import androidx.camera.testing.fakes.FakeLifecycleOwner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.GrantPermissionRule
+import androidx.test.uiautomator.UiDevice
+import androidx.testutils.RepeatRule
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = 21)
+class SwitchCameraStressTest(
+    private val cameraId: String
+) {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    @get:Rule
+    val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+    )
+
+    @get:Rule
+    val permissionRule: GrantPermissionRule =
+        GrantPermissionRule.grant(
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.RECORD_AUDIO
+        )
+
+    @get:Rule
+    val labTest: LabTestRule = LabTestRule()
+
+    @get:Rule
+    val repeatRule = RepeatRule()
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+
+    private lateinit var cameraProvider: ProcessCameraProvider
+    private lateinit var camera: Camera
+    private lateinit var cameraIdCameraSelector: CameraSelector
+
+    companion object {
+        @ClassRule
+        @JvmField val stressTest = StressTestRule()
+
+        @JvmStatic
+        @get:Parameterized.Parameters(name = "cameraId = {0}")
+        val parameters: Collection<String>
+            get() = CameraUtil.getBackwardCompatibleCameraIdListOrThrow()
+    }
+
+    @Before
+    fun setup(): Unit = runBlocking {
+        Assume.assumeTrue(CameraUtil.deviceHasCamera())
+        CoreAppTestUtil.assumeCompatibleDevice()
+        // Clear the device UI and check if there is no dialog or lock screen on the top of the
+        // window before start the test.
+        CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
+        // Use the natural orientation throughout these tests to ensure the activity isn't
+        // recreated unexpectedly. This will also freeze the sensors until
+        // mDevice.unfreezeRotation() in the tearDown() method. Any simulated rotations will be
+        // explicitly initiated from within the test.
+        device.setOrientationNatural()
+
+        cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
+
+        cameraIdCameraSelector = createCameraSelectorById(cameraId)
+
+        camera = withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(FakeLifecycleOwner(), cameraIdCameraSelector)
+        }
+    }
+
+    @After
+    fun tearDown(): Unit = runBlocking {
+        if (::cameraProvider.isInitialized) {
+            withContext(Dispatchers.Main) {
+                cameraProvider.unbindAll()
+                cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+            }
+        }
+
+        // Unfreeze rotation so the device can choose the orientation via its own policy. Be nice
+        // to other tests :)
+        device.unfreezeRotation()
+        device.pressHome()
+        device.waitForIdle(HOME_TIMEOUT_MS)
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkPreviewInEachTime_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkImageCaptureInEachTime_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkPreviewInEachTime_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkImageCaptureInEachTime_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkImageAnalysisInEachTime_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkPreviewInEachTime_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkVideoCaptureInEachTime_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkPreviewInEachTime_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkVideoCaptureInEachTime_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkImageCaptureInEachTime_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkPreviewInEachTime_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkVideoCaptureInEachTime_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun switchCamera_checkImageAnalysisInEachTime_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_checkOutput_repeatedly(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    /**
+     * Repeatedly switch the cameras and checks the use cases' capture functions can work.
+     */
+    private fun switchCamera_checkOutput_repeatedly(
+        cameraId: String,
+        useCaseCombination: Int,
+        verificationTarget: Int,
+        repeatCount: Int = STRESS_TEST_OPERATION_REPEAT_COUNT
+    ) {
+        // Launches CameraXActivity and wait for the preview ready.
+        val activityScenario =
+            launchCameraXActivityAndWaitForPreviewReady(cameraId, useCaseCombination)
+
+        // Repeatedly switches camera and checks the test target use case can capture images
+        // successfully.
+        with(activityScenario) {
+            use {
+                for (i in 1..repeatCount) {
+                    // Switches camera and wait for preview idle.
+                    switchCameraAndWaitForViewfinderIdle()
+
+                    // Checks Preview can receive frames if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_PREVIEW) != 0) {
+                        waitForViewfinderIdle()
+                    }
+
+                    // Checks ImageCapture can take a picture if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_CAPTURE) != 0) {
+                        takePictureAndWaitForImageSavedIdle()
+                    }
+
+                    // Checks VideoCapture can record a video if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_VIDEO_CAPTURE) != 0) {
+                        recordVideoAndWaitForVideoSavedIdle()
+                    }
+
+                    // Checks ImageAnalysis can receive frames if it is the test target use case.
+                    if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_ANALYSIS) != 0) {
+                        waitForImageAnalysisIdle()
+                    }
+                }
+            }
+        }
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterSwitchCameraRepeatedly_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageCapture_afterSwitchCameraRepeatedly_withPreviewImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterSwitchCameraRepeatedly_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageCapture_afterSwitchCameraRepeatedly_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageAnalysis_afterSwitchCameraRepeatedly_withPreviewImageCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_IMAGE_CAPTURE or BIND_IMAGE_ANALYSIS
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterSwitchCameraRepeatedly_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkVideoCapture_afterSwitchCameraRepeatedly_withPreviewVideoCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterSwitchCameraRepeatedly_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkVideoCapture_afterSwitchCameraRepeatedly_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageCapture_afterSwitchCameraRepeatedly_withPreviewVideoCaptureImageCapture() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_CAPTURE
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkVideoCapture_afterSwitchCameraRepeatedly_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_VIDEO_CAPTURE
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkImageAnalysis_afterSwitchCameraRepeatedly_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_IMAGE_ANALYSIS
+        )
+    }
+
+    @LabTestRule.LabTestOnly
+    @Test
+    @RepeatRule.Repeat(times = STRESS_TEST_REPEAT_COUNT)
+    fun checkPreview_afterSwitchCameraRepeatedly_withPreviewVideoCaptureImageAnalysis() {
+        val useCaseCombination = BIND_PREVIEW or BIND_VIDEO_CAPTURE or BIND_IMAGE_ANALYSIS
+        assumeBothLensFacingCamerasSupportUseCaseCombination(camera, useCaseCombination)
+        switchCamera_repeatedly_thenCheckOutput(
+            cameraId,
+            useCaseCombination,
+            VERIFICATION_TARGET_PREVIEW
+        )
+    }
+
+    /**
+     * Switch the cameras repeatedly,and then checks the use cases' capture functions can work.
+     */
+    private fun switchCamera_repeatedly_thenCheckOutput(
+        cameraId: String,
+        useCaseCombination: Int,
+        verificationTarget: Int,
+        repeatCount: Int = STRESS_TEST_OPERATION_REPEAT_COUNT
+    ) {
+        // Launches CameraXActivity and wait for the preview ready.
+        val activityScenario =
+            launchCameraXActivityAndWaitForPreviewReady(cameraId, useCaseCombination)
+
+        // Switches camera repeatedly, and then checks the test target use case can capture images
+        // successfully.
+        with(activityScenario) {
+            use {
+                for (i in 1..repeatCount) {
+                    // Switches camera and wait for preview idle.
+                    switchCameraAndWaitForViewfinderIdle()
+                }
+
+                // Checks Preview can receive frames if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_PREVIEW) != 0) {
+                    waitForViewfinderIdle()
+                }
+
+                // Checks ImageCapture can take a picture if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_CAPTURE) != 0) {
+                    takePictureAndWaitForImageSavedIdle()
+                }
+
+                // Checks VideoCapture can record a video if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_VIDEO_CAPTURE) != 0) {
+                    recordVideoAndWaitForVideoSavedIdle()
+                }
+
+                // Checks ImageAnalysis can receive frames if it is the test target use case.
+                if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_ANALYSIS) != 0) {
+                    waitForImageAnalysisIdle()
+                }
+            }
+        }
+    }
+
+    private fun assumeBothLensFacingCamerasSupportUseCaseCombination(
+        camera: Camera,
+        useCaseCombination: Int
+    ): Unit = runBlocking {
+        // Checks whether the input camera can support the use case combination
+        assumeCameraSupportUseCaseCombination(camera, useCaseCombination)
+
+        val camera2CameraInfo = Camera2CameraInfo.from(camera.cameraInfo)
+        val lensFacing =
+            camera2CameraInfo.getCameraCharacteristic(CameraCharacteristics.LENS_FACING)
+
+        val otherLensFacingCameraSelector =
+            if (lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+                CameraSelector.DEFAULT_FRONT_CAMERA
+            } else {
+                CameraSelector.DEFAULT_BACK_CAMERA
+            }
+
+        val otherLensFacingCamera = withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(FakeLifecycleOwner(), otherLensFacingCameraSelector)
+        }
+
+        // Checks whether the camera of the other lens facing can support the use case combination
+        assumeCameraSupportUseCaseCombination(otherLensFacingCamera, useCaseCombination)
+    }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt
index 31bd201..8342a50 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt
@@ -16,15 +16,120 @@
 
 package androidx.camera.integration.core.util
 
+import android.content.Context
+import android.content.Intent
 import androidx.annotation.OptIn
 import androidx.camera.camera2.interop.Camera2CameraInfo
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.camera.core.Camera
 import androidx.camera.core.CameraFilter
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.integration.core.CameraXActivity
+import androidx.camera.integration.core.CameraXActivity.BIND_IMAGE_ANALYSIS
+import androidx.camera.integration.core.CameraXActivity.BIND_IMAGE_CAPTURE
+import androidx.camera.integration.core.CameraXActivity.BIND_PREVIEW
+import androidx.camera.integration.core.CameraXActivity.BIND_VIDEO_CAPTURE
+import androidx.camera.integration.core.CameraXActivity.INTENT_EXTRA_CAMERA_ID
+import androidx.camera.integration.core.CameraXActivity.INTENT_EXTRA_USE_CASE_COMBINATION
+import androidx.camera.integration.core.waitForViewfinderIdle
+import androidx.camera.video.Recorder
+import androidx.camera.video.VideoCapture
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assume.assumeTrue
+
+private const val CORE_TEST_APP_PACKAGE = "androidx.camera.integration.core"
 
 object StressTestUtil {
 
+    /**
+     * Launches CameraXActivity and wait for the preview ready.
+     *
+     * <p>Test cases can start activity by this function and then add other specific test
+     * operations after the activity is launched.
+     *
+     * <p>If the target camera device can't support the specified use case combination, an
+     * AssumptionViolatedException will be thrown to skip the test.
+     *
+     * @param cameraId Launches the activity with the specified camera id
+     * @param useCaseCombination Launches the activity with the specified use case combination.
+     * [BIND_PREVIEW], [BIND_IMAGE_CAPTURE], [BIND_VIDEO_CAPTURE] and [BIND_IMAGE_ANALYSIS] can be
+     * used to set the combination.
+     */
+    @JvmStatic
+    fun launchCameraXActivityAndWaitForPreviewReady(
+        cameraId: String,
+        useCaseCombination: Int
+    ): ActivityScenario<CameraXActivity> {
+        if (useCaseCombination.and(BIND_PREVIEW) == 0) {
+            throw IllegalArgumentException("Preview must be included!")
+        }
+
+        val intent = ApplicationProvider.getApplicationContext<Context>().packageManager
+            .getLaunchIntentForPackage(CORE_TEST_APP_PACKAGE)!!.apply {
+                putExtra(INTENT_EXTRA_CAMERA_ID, cameraId)
+                putExtra(INTENT_EXTRA_USE_CASE_COMBINATION, useCaseCombination)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+            }
+
+        val activityScenario: ActivityScenario<CameraXActivity> = ActivityScenario.launch(intent)
+
+        activityScenario.onActivity {
+            // Checks that the camera id is correct
+            val camera2CameraInfo = Camera2CameraInfo.from(it.camera!!.cameraInfo)
+
+            if (camera2CameraInfo.cameraId != cameraId) {
+                it.finish()
+                throw IllegalArgumentException("The activity is not launched with the correct" +
+                    " camera of expected id.")
+            }
+        }
+
+        // Ensure ActivityScenario is cleaned up properly
+        // Wait for viewfinder to receive enough frames for its IdlingResource to idle.
+        activityScenario.waitForViewfinderIdle()
+
+        return activityScenario
+    }
+
+    /**
+     * Checks and skips the test if the target camera can't support the use case combination.
+     */
+    @JvmStatic
+    fun assumeCameraSupportUseCaseCombination(camera: Camera, useCaseCombination: Int) {
+        val preview = Preview.Builder().build()
+        val imageCapture = if (useCaseCombination.and(BIND_IMAGE_CAPTURE) != 0) {
+            ImageCapture.Builder().build()
+        } else {
+            null
+        }
+        val videoCapture = if (useCaseCombination.and(BIND_VIDEO_CAPTURE) != 0) {
+            VideoCapture.withOutput(Recorder.Builder().build())
+        } else {
+            null
+        }
+        val imageAnalysis = if (useCaseCombination.and(BIND_IMAGE_ANALYSIS) != 0) {
+            ImageAnalysis.Builder().build()
+        } else {
+            null
+        }
+
+        assumeTrue(
+            camera.isUseCasesCombinationSupported(
+                *listOfNotNull(
+                    preview,
+                    imageCapture,
+                    videoCapture,
+                    imageAnalysis
+                ).toTypedArray()
+            )
+        )
+    }
+
     @JvmStatic
     @OptIn(ExperimentalCamera2Interop::class)
     fun createCameraSelectorById(cameraId: String) =
@@ -58,4 +163,34 @@
      *
      */
     const val STRESS_TEST_OPERATION_REPEAT_COUNT = 10
+
+    /**
+     * Timeout duration to wait for idle after pressing HOME key
+     */
+    const val HOME_TIMEOUT_MS = 3000L
+
+    /**
+     * Auto-stop duration for video capture related tests.
+     */
+    const val VIDEO_CAPTURE_AUTO_STOP_LENGTH_MS = 3000L
+
+    /**
+     * Constant to specify that the verification target is [Preview].
+     */
+    const val VERIFICATION_TARGET_PREVIEW = 0x1
+
+    /**
+     * Constant to specify that the verification target is [ImageCapture].
+     */
+    const val VERIFICATION_TARGET_IMAGE_CAPTURE = 0x2
+
+    /**
+     * Constant to specify that the verification target is [VideoCapture].
+     */
+    const val VERIFICATION_TARGET_VIDEO_CAPTURE = 0x4
+
+    /**
+     * Constant to specify that the verification target is [ImageAnalysis].
+     */
+    const val VERIFICATION_TARGET_IMAGE_ANALYSIS = 0x8
 }
\ No newline at end of file
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index afabb4a..f35f604 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -29,8 +29,10 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.display.DisplayManager;
 import android.media.MediaScannerConnection;
 import android.net.Uri;
@@ -65,14 +67,18 @@
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.camera.camera2.internal.compat.quirk.CrashWhenTakingPhotoWithAutoFlashAEModeQuirk;
 import androidx.camera.camera2.internal.compat.quirk.ImageCaptureFailWithAutoFlashQuirk;
 import androidx.camera.camera2.internal.compat.quirk.ImageCaptureFlashNotFireQuirk;
+import androidx.camera.camera2.interop.Camera2CameraInfo;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraControl;
+import androidx.camera.core.CameraFilter;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.DisplayOrientedMeteringPointFactory;
@@ -155,6 +161,25 @@
     // "default_test_case".
     private static final String INTENT_EXTRA_E2E_TEST_CASE = "e2e_test_case";
     public static final String INTENT_EXTRA_CAMERA_IMPLEMENTATION = "camera_implementation";
+    // Launch the activity with the specified camera id.
+    @VisibleForTesting
+    public static final String INTENT_EXTRA_CAMERA_ID = "camera_id";
+    // Launch the activity with the specified use case combination.
+    @VisibleForTesting
+    public static final String INTENT_EXTRA_USE_CASE_COMBINATION = "use_case_combination";
+    @VisibleForTesting
+    // Sets this bit to bind Preview when using INTENT_EXTRA_USE_CASE_COMBINATION
+    public static final int BIND_PREVIEW = 0x1;
+    @VisibleForTesting
+    // Sets this bit to bind ImageCapture when using INTENT_EXTRA_USE_CASE_COMBINATION
+    public static final int BIND_IMAGE_CAPTURE = 0x2;
+    @VisibleForTesting
+    // Sets this bit to bind VideoCapture when using INTENT_EXTRA_USE_CASE_COMBINATION
+    public static final int BIND_VIDEO_CAPTURE = 0x4;
+    @VisibleForTesting
+    // Sets this bit to bind ImageAnalysis when using INTENT_EXTRA_USE_CASE_COMBINATION
+    public static final int BIND_IMAGE_ANALYSIS = 0x8;
+    private static final int UNKNOWN_LENS_FACING = -1;
     static final CameraSelector BACK_SELECTOR =
             new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
     static final CameraSelector FRONT_SELECTOR =
@@ -163,6 +188,9 @@
 
     private final AtomicLong mImageAnalysisFrameCount = new AtomicLong(0);
     private final AtomicLong mPreviewFrameCount = new AtomicLong(0);
+    // Automatically stops the video recording when this length value is set to be non-zero and
+    // video length reaches the length in ms.
+    private long mVideoCaptureAutoStopLength = 0;
     final MutableLiveData<String> mImageAnalysisResult = new MutableLiveData<>();
     private static final String BACKWARD = "BACKWARD";
     private static final String SWITCH_TEST_CASE = "switch_test_case";
@@ -183,6 +211,9 @@
     ExecutorService mImageCaptureExecutorService;
     Camera mCamera;
 
+    private CameraSelector mLaunchingCameraIdSelector = null;
+    private int mLaunchingCameraLensFacing = UNKNOWN_LENS_FACING;
+
     private ToggleButton mVideoToggle;
     private ToggleButton mPhotoToggle;
     private ToggleButton mAnalysisToggle;
@@ -208,7 +239,8 @@
     private RecordUi mRecordUi;
     private Quality mVideoQuality;
 
-    SessionImagesUriSet mSessionImagesUriSet = new SessionImagesUriSet();
+    SessionMediaUriSet mSessionImagesUriSet = new SessionMediaUriSet();
+    SessionMediaUriSet mSessionVideosUriSet = new SessionMediaUriSet();
 
     // Analyzer to be used with ImageAnalysis.
     private ImageAnalysis.Analyzer mAnalyzer = new ImageAnalysis.Analyzer() {
@@ -220,7 +252,8 @@
             mImageAnalysisResult.setValue(
                     Long.toString(image.getImageInfo().getTimestamp()));
             try {
-                if (!mAnalysisIdlingResource.isIdleNow()) {
+                if (mImageAnalysisFrameCount.get() >= FRAMES_UNTIL_IMAGE_ANALYSIS_IS_READY
+                        && !mAnalysisIdlingResource.isIdleNow()) {
                     mAnalysisIdlingResource.decrement();
                 }
             } catch (IllegalStateException e) {
@@ -270,13 +303,17 @@
 
     // Espresso testing variables
     private static final int FRAMES_UNTIL_VIEW_IS_READY = 5;
+    // Espresso testing variables
+    private static final int FRAMES_UNTIL_IMAGE_ANALYSIS_IS_READY = 5;
     private final CountingIdlingResource mViewIdlingResource = new CountingIdlingResource("view");
     private final CountingIdlingResource mInitializationIdlingResource =
             new CountingIdlingResource("initialization");
-    final CountingIdlingResource mAnalysisIdlingResource =
+    private final CountingIdlingResource mAnalysisIdlingResource =
             new CountingIdlingResource("analysis");
-    final CountingIdlingResource mImageSavedIdlingResource =
+    private final CountingIdlingResource mImageSavedIdlingResource =
             new CountingIdlingResource("imagesaved");
+    private final CountingIdlingResource mVideoSavedIdlingResource =
+            new CountingIdlingResource("videosaved");
 
     /**
      * Retrieve idling resource that waits for image received by analyzer).
@@ -306,6 +343,15 @@
     }
 
     /**
+     * Retrieve idling resource that waits for a video being recorded and saved.
+     */
+    @VisibleForTesting
+    @NonNull
+    public IdlingResource getVideoSavedIdlingResource() {
+        return mVideoSavedIdlingResource;
+    }
+
+    /**
      * Retrieve idling resource that waits for initialization to finish.
      */
     @VisibleForTesting
@@ -336,7 +382,32 @@
     public void resetViewIdlingResource() {
         mPreviewFrameCount.set(0);
         // Make the view idling resource non-idle, until required framecount achieved.
-        mViewIdlingResource.increment();
+        if (mViewIdlingResource.isIdleNow()) {
+            mViewIdlingResource.increment();
+        }
+    }
+
+    /**
+     * Retrieve idling resource that waits for ImageAnalysis to receive images.
+     */
+    @VisibleForTesting
+    public void resetAnalysisIdlingResource() {
+        mImageAnalysisFrameCount.set(0);
+        // Make the analysis idling resource non-idle, until required images achieved.
+        if (mAnalysisIdlingResource.isIdleNow()) {
+            mAnalysisIdlingResource.increment();
+        }
+    }
+
+    /**
+     * Retrieve idling resource that waits for VideoCapture to record a video.
+     */
+    @VisibleForTesting
+    public void resetVideoSavedIdlingResource() {
+        // Make the video saved idling resource non-idle, until required video length recorded.
+        if (mVideoSavedIdlingResource.isIdleNow()) {
+            mVideoSavedIdlingResource.increment();
+        }
     }
 
     /**
@@ -348,6 +419,13 @@
         mSessionImagesUriSet.deleteAllUris();
     }
 
+    /**
+     * Delete videos that were taking during this session so far.
+     */
+    @VisibleForTesting
+    public void deleteSessionVideos() {
+        mSessionVideosUriSet.deleteAllUris();
+    }
 
     @ImageCapture.CaptureMode
     int getCaptureMode() {
@@ -425,6 +503,9 @@
                         pendingRecording = getVideoCapture().getOutput().prepareRecording(
                                 this, getNewVideoOutputMediaStoreOptions());
                     }
+
+                    resetVideoSavedIdlingResource();
+
                     mActiveRecording = pendingRecording
                             .withAudioEnabled()
                             .start(ContextCompat.getMainExecutor(CameraXActivity.this),
@@ -531,12 +612,14 @@
                                 getApplicationContext().getContentResolver(),
                                 uri
                         );
+                        updateVideoSavedSessionData(uri);
                     } else if (outputOptions instanceof FileOutputOptions) {
                         videoFilePath = ((FileOutputOptions) outputOptions).getFile().getPath();
                         MediaScannerConnection.scanFile(this,
                                 new String[] { videoFilePath }, null,
                                 (path, uri1) -> {
                                     Log.i(TAG, "Scanned " + path + " -> uri= " + uri1);
+                                    updateVideoSavedSessionData(uri1);
                                 });
                         msg = "Saved file " + videoFilePath;
                     } else {
@@ -562,6 +645,16 @@
         }
     };
 
+    private void updateVideoSavedSessionData(@NonNull Uri uri) {
+        if (mSessionVideosUriSet != null) {
+            mSessionVideosUriSet.add(uri);
+        }
+
+        if (!mVideoSavedIdlingResource.isIdleNow()) {
+            mVideoSavedIdlingResource.decrement();
+        }
+    }
+
     @NonNull
     private MediaStoreOutputOptions getNewVideoOutputMediaStoreOptions() {
         String videoFileName = "video_" + System.currentTimeMillis();
@@ -589,12 +682,16 @@
     }
 
     private void updateRecordingStats(@NonNull RecordingStats stats) {
-        double durationSec = TimeUnit.NANOSECONDS.toMillis(stats.getRecordedDurationNanos())
-                / 1000d;
+        double durationMs = TimeUnit.NANOSECONDS.toMillis(stats.getRecordedDurationNanos());
         // Show megabytes in International System of Units (SI)
         double sizeMb = stats.getNumBytesRecorded() / (1000d * 1000d);
-        String msg = String.format("%.2f sec\n%.2f MB", durationSec, sizeMb);
+        String msg = String.format("%.2f sec\n%.2f MB", durationMs / 1000d, sizeMb);
         mRecordUi.getTextStats().setText(msg);
+
+        if (mVideoCaptureAutoStopLength > 0 && durationMs >= mVideoCaptureAutoStopLength
+                && mRecordUi.getState() == RecordUi.State.RECORDING) {
+            mRecordUi.getButtonRecord().callOnClick();
+        }
     }
 
     private void setUpTakePictureButton() {
@@ -655,16 +752,60 @@
     @SuppressWarnings("ObjectToString")
     private void setUpCameraDirectionButton() {
         mCameraDirectionButton.setOnClickListener(v -> {
-            if (mCurrentCameraSelector == BACK_SELECTOR) {
-                mCurrentCameraSelector = FRONT_SELECTOR;
-            } else if (mCurrentCameraSelector == FRONT_SELECTOR) {
-                mCurrentCameraSelector = BACK_SELECTOR;
-            }
             Log.d(TAG, "Change camera direction: " + mCurrentCameraSelector);
-            tryBindUseCases();
+            CameraSelector switchedCameraSelector =
+                    getSwitchedCameraSelector(mCurrentCameraSelector);
+
+            if (isUseCasesCombinationSupported(switchedCameraSelector, mUseCases)) {
+                mCurrentCameraSelector = switchedCameraSelector;
+                tryBindUseCases();
+            } else {
+                String msg = "Camera of the other lens facing can't support current use case "
+                        + "combination.";
+                Log.d(TAG, msg);
+                Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
+            }
         });
     }
 
+    @NonNull
+    private CameraSelector getSwitchedCameraSelector(
+            @NonNull CameraSelector currentCameraSelector) {
+        CameraSelector switchedCameraSelector;
+        // When the activity is launched with a specific camera id, camera switch function
+        // will switch the cameras between the camera of the specified camera id and the
+        // default camera of the opposite lens facing.
+        if (mLaunchingCameraIdSelector != null) {
+            if (currentCameraSelector != mLaunchingCameraIdSelector) {
+                switchedCameraSelector = mLaunchingCameraIdSelector;
+            } else {
+                if (mLaunchingCameraLensFacing == CameraSelector.LENS_FACING_BACK) {
+                    switchedCameraSelector = FRONT_SELECTOR;
+                } else {
+                    switchedCameraSelector = BACK_SELECTOR;
+                }
+            }
+        } else {
+            if (currentCameraSelector == BACK_SELECTOR) {
+                switchedCameraSelector = FRONT_SELECTOR;
+            } else {
+                switchedCameraSelector = BACK_SELECTOR;
+            }
+        }
+
+        return switchedCameraSelector;
+    }
+
+    private boolean isUseCasesCombinationSupported(@NonNull CameraSelector cameraSelector,
+            @NonNull List<UseCase> useCases) {
+        if (mCameraProvider == null) {
+            throw new IllegalStateException("Need to obtain mCameraProvider first!");
+        }
+
+        Camera targetCamera = mCameraProvider.bindToLifecycle(this, cameraSelector);
+        return targetCamera.isUseCasesCombinationSupported(useCases.toArray(new UseCase[0]));
+    }
+
     private void setUpTorchButton() {
         mTorchButton.setOnClickListener(v -> {
             Objects.requireNonNull(getCameraInfo());
@@ -817,6 +958,25 @@
         mZslToggle.setOnCheckedChangeListener(mOnCheckedChangeListener);
     }
 
+    private void updateUseCaseCombinationByIntent(@NonNull Intent intent) {
+        Bundle bundle = intent.getExtras();
+
+        if (bundle == null) {
+            return;
+        }
+
+        int useCaseCombination = bundle.getInt(INTENT_EXTRA_USE_CASE_COMBINATION, 0);
+
+        if (useCaseCombination == 0) {
+            return;
+        }
+
+        mPreviewToggle.setChecked((useCaseCombination & BIND_PREVIEW) != 0L);
+        mPhotoToggle.setChecked((useCaseCombination & BIND_IMAGE_CAPTURE) != 0L);
+        mVideoToggle.setChecked((useCaseCombination & BIND_VIDEO_CAPTURE) != 0L);
+        mAnalysisToggle.setChecked((useCaseCombination & BIND_IMAGE_ANALYSIS) != 0L);
+    }
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -835,6 +995,8 @@
         mAnalysisToggle = findViewById(R.id.AnalysisToggle);
         mPreviewToggle = findViewById(R.id.PreviewToggle);
 
+        updateUseCaseCombinationByIntent(getIntent());
+
         mTakePicture = findViewById(R.id.Picture);
         mFlashButton = findViewById(R.id.flash_toggle);
         mCameraDirectionButton = findViewById(R.id.direction_toggle);
@@ -904,12 +1066,19 @@
         // Get params from adb extra string
         Bundle bundle = this.getIntent().getExtras();
         if (bundle != null) {
-            String newCameraDirection = bundle.getString(INTENT_EXTRA_CAMERA_DIRECTION);
-            if (newCameraDirection != null) {
-                if (newCameraDirection.equals(BACKWARD)) {
-                    mCurrentCameraSelector = BACK_SELECTOR;
-                } else {
-                    mCurrentCameraSelector = FRONT_SELECTOR;
+            String launchingCameraId = bundle.getString(INTENT_EXTRA_CAMERA_ID, null);
+
+            if (launchingCameraId != null) {
+                mLaunchingCameraIdSelector = createCameraSelectorById(launchingCameraId);
+                mCurrentCameraSelector = mLaunchingCameraIdSelector;
+            } else {
+                String newCameraDirection = bundle.getString(INTENT_EXTRA_CAMERA_DIRECTION);
+                if (newCameraDirection != null) {
+                    if (newCameraDirection.equals(BACKWARD)) {
+                        mCurrentCameraSelector = BACK_SELECTOR;
+                    } else {
+                        mCurrentCameraSelector = FRONT_SELECTOR;
+                    }
                 }
             }
 
@@ -963,6 +1132,7 @@
      *
      * @param calledBySelf flag indicates if this is a recursive call.
      */
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     void tryBindUseCases(boolean calledBySelf) {
         boolean isViewFinderReady = mViewFinder.getWidth() != 0 && mViewFinder.getHeight() != 0;
         boolean isCameraReady = mCameraProvider != null;
@@ -990,8 +1160,23 @@
 
         mCameraProvider.unbindAll();
         try {
+            // Binds to lifecycle without use cases to make sure mCamera can be retrieved for
+            // tests to do necessary checks.
+            mCamera = mCameraProvider.bindToLifecycle(this, mCurrentCameraSelector);
+
+            // Retrieves the lens facing info when the activity is launched with a specified
+            // camera id.
+            if (mCurrentCameraSelector == mLaunchingCameraIdSelector
+                    && mLaunchingCameraLensFacing == UNKNOWN_LENS_FACING) {
+                Camera2CameraInfo camera2CameraInfo =
+                        Camera2CameraInfo.from(mCamera.getCameraInfo());
+                mLaunchingCameraLensFacing = camera2CameraInfo.getCameraCharacteristic(
+                        CameraCharacteristics.LENS_FACING);
+            }
+
             List<UseCase> useCases = buildUseCases();
             mCamera = bindToLifecycleSafely(useCases);
+
             // Set the use cases after a successful binding.
             mUseCases = useCases;
         } catch (IllegalArgumentException ex) {
@@ -1054,8 +1239,8 @@
                     .setTargetName("ImageAnalysis")
                     .build();
             useCases.add(imageAnalysis);
-            // Make the analysis idling resource non-idle, until a frame received.
-            mAnalysisIdlingResource.increment();
+            // Make the analysis idling resource non-idle, until the required frames received.
+            resetAnalysisIdlingResource();
             imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), mAnalyzer);
         }
 
@@ -1358,20 +1543,20 @@
         }
     }
 
-    private class SessionImagesUriSet {
-        private final Set<Uri> mSessionImages;
+    private class SessionMediaUriSet {
+        private final Set<Uri> mSessionMediaUris;
 
-        SessionImagesUriSet() {
-            mSessionImages = Collections.synchronizedSet(new HashSet<>());
+        SessionMediaUriSet() {
+            mSessionMediaUris = Collections.synchronizedSet(new HashSet<>());
         }
 
         public void add(@NonNull Uri uri) {
-            mSessionImages.add(uri);
+            mSessionMediaUris.add(uri);
         }
 
         public void deleteAllUris() {
-            synchronized (mSessionImages) {
-                Iterator<Uri> it = mSessionImages.iterator();
+            synchronized (mSessionMediaUris) {
+                Iterator<Uri> it = mSessionMediaUris.iterator();
                 while (it.hasNext()) {
                     getContentResolver().delete(it.next(), null, null);
                     it.remove();
@@ -1501,6 +1686,11 @@
         return findUseCase(VideoCapture.class);
     }
 
+    @VisibleForTesting
+    void setVideoCaptureAutoStopLength(long autoStopLengthInMs) {
+        mVideoCaptureAutoStopLength = autoStopLengthInMs;
+    }
+
     /**
      * Finds the use case by the given class.
      */
@@ -1518,6 +1708,12 @@
 
     @VisibleForTesting
     @Nullable
+    public Camera getCamera() {
+        return mCamera;
+    }
+
+    @VisibleForTesting
+    @Nullable
     CameraInfo getCameraInfo() {
         return mCamera != null ? mCamera.getCameraInfo() : null;
     }
@@ -1593,4 +1789,21 @@
                 throw new IllegalArgumentException("Undefined item id: " + itemId);
         }
     }
+
+    private static CameraSelector createCameraSelectorById(@Nullable String cameraId) {
+        return new CameraSelector.Builder().addCameraFilter(new CameraFilter() {
+            @NonNull
+            @Override
+            @OptIn(markerClass = ExperimentalCamera2Interop.class)
+            public List<CameraInfo> filter(@NonNull List<CameraInfo> cameraInfos) {
+                for (CameraInfo cameraInfo : cameraInfos) {
+                    if (cameraId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
+                        return Collections.singletonList(cameraInfo);
+                    }
+                }
+
+                throw new IllegalArgumentException("No camera can be find for id: " + cameraId);
+            }
+        }).build();
+    }
 }
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
index ba07cb2..ca77bfa 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
@@ -20,7 +20,6 @@
 import android.content.Context
 import android.content.Intent
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.extensions.ExtensionMode
 import androidx.camera.integration.extensions.util.ExtensionsTestUtil
 import androidx.camera.integration.extensions.util.ExtensionsTestUtil.STRESS_TEST_OPERATION_REPEAT_COUNT
 import androidx.camera.testing.CameraUtil
@@ -40,6 +39,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
+import androidx.test.uiautomator.UiDevice
 import androidx.testutils.RepeatRule
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -63,6 +63,7 @@
     private val cameraId: String,
     private val extensionMode: Int
 ) {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
 
     @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
@@ -91,21 +92,28 @@
 
         @Parameterized.Parameters(name = "cameraId = {0}, extensionMode = {1}")
         @JvmStatic
-        fun parameters() = ExtensionsTestUtil.getAllCameraIdModeCombinations()
+        fun parameters() = ExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
     }
 
     @Before
     fun setUp() {
-        if (extensionMode != ExtensionMode.NONE) {
-            assumeTrue(ExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
-        }
+        assumeTrue(ExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
         // Clear the device UI and check if there is no dialog or lock screen on the top of the
         // window before starting the test.
         CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
+        // Use the natural orientation throughout these tests to ensure the activity isn't
+        // recreated unexpectedly. This will also freeze the sensors until
+        // mDevice.unfreezeRotation() in the tearDown() method. Any simulated rotations will be
+        // explicitly initiated from within the test.
+        device.setOrientationNatural()
     }
 
     @After
     fun tearDown() {
+        // Unfreeze rotation so the device can choose the orientation via its own policy. Be nice
+        // to other tests :)
+        device.unfreezeRotation()
+
         if (::activityScenario.isInitialized) {
             activityScenario.onActivity { it.finish() }
         }
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
index 9248ad0..1c35c37 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
@@ -38,6 +38,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
+import androidx.test.uiautomator.UiDevice
 import androidx.testutils.RepeatRule
 import org.junit.After
 import org.junit.Assume.assumeTrue
@@ -56,6 +57,7 @@
 @LargeTest
 @RunWith(Parameterized::class)
 class SwitchAvailableModesStressTest(private val cameraId: String) {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
 
     @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
@@ -93,10 +95,19 @@
         // Clear the device UI and check if there is no dialog or lock screen on the top of the
         // window before starting the test.
         CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
+        // Use the natural orientation throughout these tests to ensure the activity isn't
+        // recreated unexpectedly. This will also freeze the sensors until
+        // mDevice.unfreezeRotation() in the tearDown() method. Any simulated rotations will be
+        // explicitly initiated from within the test.
+        device.setOrientationNatural()
     }
 
     @After
     fun tearDown() {
+        // Unfreeze rotation so the device can choose the orientation via its own policy. Be nice
+        // to other tests :)
+        device.unfreezeRotation()
+
         if (::activityScenario.isInitialized) {
             activityScenario.onActivity { it.finish() }
         }
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
index f3c09c0..36b9064 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
@@ -39,6 +39,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
+import androidx.test.uiautomator.UiDevice
 import androidx.testutils.RepeatRule
 import org.junit.After
 import org.junit.Assume.assumeTrue
@@ -57,6 +58,7 @@
 @LargeTest
 @RunWith(Parameterized::class)
 class SwitchCameraStressTest(private val extensionMode: Int) {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
 
     @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
@@ -86,7 +88,6 @@
         @Parameterized.Parameters(name = "extensionMode = {0}")
         @JvmStatic
         fun parameters() = arrayOf(
-            ExtensionMode.NONE,
             ExtensionMode.BOKEH,
             ExtensionMode.HDR,
             ExtensionMode.NIGHT,
@@ -97,16 +98,23 @@
 
     @Before
     fun setUp() {
-        if (extensionMode != ExtensionMode.NONE) {
-            assumeTrue(ExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
-        }
+        assumeTrue(ExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
         // Clear the device UI and check if there is no dialog or lock screen on the top of the
         // window before starting the test.
         CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
+        // Use the natural orientation throughout these tests to ensure the activity isn't
+        // recreated unexpectedly. This will also freeze the sensors until
+        // mDevice.unfreezeRotation() in the tearDown() method. Any simulated rotations will be
+        // explicitly initiated from within the test.
+        device.setOrientationNatural()
     }
 
     @After
     fun tearDown() {
+        // Unfreeze rotation so the device can choose the orientation via its own policy. Be nice
+        // to other tests :)
+        device.unfreezeRotation()
+
         if (::activityScenario.isInitialized) {
             activityScenario.onActivity { it.finish() }
         }
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 70c8b44..11f94b4 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -631,6 +631,7 @@
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
     ctor public TextStyle(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
     method public androidx.glance.unit.ColorProvider? getColor();
     method public androidx.compose.ui.unit.TextUnit? getFontSize();
     method public androidx.glance.text.FontStyle? getFontStyle();
diff --git a/glance/glance/api/public_plus_experimental_current.txt b/glance/glance/api/public_plus_experimental_current.txt
index 70c8b44..11f94b4 100644
--- a/glance/glance/api/public_plus_experimental_current.txt
+++ b/glance/glance/api/public_plus_experimental_current.txt
@@ -631,6 +631,7 @@
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
     ctor public TextStyle(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
     method public androidx.glance.unit.ColorProvider? getColor();
     method public androidx.compose.ui.unit.TextUnit? getFontSize();
     method public androidx.glance.text.FontStyle? getFontStyle();
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 70c8b44..11f94b4 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -631,6 +631,7 @@
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
     ctor public TextStyle(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
     method public androidx.glance.unit.ColorProvider? getColor();
     method public androidx.compose.ui.unit.TextUnit? getFontSize();
     method public androidx.glance.text.FontStyle? getFontStyle();
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt
index f1c6d5b..be3f8b2 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt
@@ -32,6 +32,22 @@
     val textAlign: TextAlign? = null,
     val textDecoration: TextDecoration? = null,
 ) {
+    fun copy(
+        color: ColorProvider? = this.color,
+        fontSize: TextUnit? = this.fontSize,
+        fontWeight: FontWeight? = this.fontWeight,
+        fontStyle: FontStyle? = this.fontStyle,
+        textAlign: TextAlign? = this.textAlign,
+        textDecoration: TextDecoration? = this.textDecoration
+    ) = TextStyle(
+        color = color,
+        fontSize = fontSize,
+        fontWeight = fontWeight,
+        fontStyle = fontStyle,
+        textAlign = textAlign,
+        textDecoration = textDecoration
+    )
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is TextStyle) return false
diff --git a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/TestCasesGenerator.java b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/TestCasesGenerator.java
index 9738382..8a5ca91 100644
--- a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/TestCasesGenerator.java
+++ b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/TestCasesGenerator.java
@@ -28,12 +28,15 @@
 import androidx.wear.tiles.ActionBuilders.LaunchAction;
 import androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters;
 import androidx.wear.tiles.LayoutElementBuilders.Box;
+import androidx.wear.tiles.LayoutElementBuilders.Column;
 import androidx.wear.tiles.LayoutElementBuilders.LayoutElement;
+import androidx.wear.tiles.LayoutElementBuilders.Spacer;
 import androidx.wear.tiles.ModifiersBuilders.Background;
 import androidx.wear.tiles.ModifiersBuilders.Clickable;
 import androidx.wear.tiles.ModifiersBuilders.Modifiers;
 import androidx.wear.tiles.material.Button;
 import androidx.wear.tiles.material.ButtonDefaults;
+import androidx.wear.tiles.material.Chip;
 import androidx.wear.tiles.material.ChipColors;
 import androidx.wear.tiles.material.CircularProgressIndicator;
 import androidx.wear.tiles.material.Colors;
@@ -129,6 +132,33 @@
                         .setPrimaryChipContent(primaryChipBuilder.build())
                         .setContent(buildColoredBox(Color.YELLOW))
                         .build());
+        testCases.put(
+                "two_chips_content_primarychiplayout_golden" + goldenSuffix,
+                new PrimaryLayout.Builder(deviceParameters)
+                        .setPrimaryChipContent(primaryChipBuilder.build())
+                        .setContent(
+                                new Column.Builder()
+                                        .setWidth(expand())
+                                        .setHeight(expand())
+                                        .addContent(
+                                                new Chip.Builder(
+                                                                context,
+                                                                clickable,
+                                                                deviceParameters)
+                                                        .setPrimaryLabelContent("First chip")
+                                                        .setWidth(expand())
+                                                        .build())
+                                        .addContent(new Spacer.Builder().setHeight(dp(4)).build())
+                                        .addContent(
+                                                new Chip.Builder(
+                                                                context,
+                                                                clickable,
+                                                                deviceParameters)
+                                                        .setPrimaryLabelContent("Second chip")
+                                                        .setWidth(expand())
+                                                        .build())
+                                        .build())
+                        .build());
 
         primaryChipBuilder =
                 new CompactChip.Builder(context, "Action", clickable, deviceParameters);
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java
index 192ad83..e75135e 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/ChipDefaults.java
@@ -46,6 +46,15 @@
     public static final DpProp COMPACT_HEIGHT = dp(32);
 
     /**
+     * The default height of tappable area for standard {@link CompactChip}
+     *
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    public static final DpProp COMPACT_HEIGHT_TAPPABLE = dp(48);
+
+    /**
      * The default height for standard {@link TitleChip}
      *
      * @hide
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/CompactChip.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/CompactChip.java
index a7db44e8..21bb316 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/CompactChip.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/CompactChip.java
@@ -16,12 +16,15 @@
 
 package androidx.wear.tiles.material;
 
+import static androidx.wear.tiles.DimensionBuilders.wrap;
 import static androidx.wear.tiles.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER;
 import static androidx.wear.tiles.material.ChipDefaults.COMPACT_HEIGHT;
+import static androidx.wear.tiles.material.ChipDefaults.COMPACT_HEIGHT_TAPPABLE;
 import static androidx.wear.tiles.material.ChipDefaults.COMPACT_HORIZONTAL_PADDING;
 import static androidx.wear.tiles.material.ChipDefaults.COMPACT_PRIMARY_COLORS;
 import static androidx.wear.tiles.material.Helper.checkNotNull;
 import static androidx.wear.tiles.material.Helper.checkTag;
+import static androidx.wear.tiles.material.Helper.getTagBytes;
 
 import android.content.Context;
 
@@ -30,10 +33,12 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters;
-import androidx.wear.tiles.DimensionBuilders.WrappedDimensionProp;
+import androidx.wear.tiles.LayoutElementBuilders;
 import androidx.wear.tiles.LayoutElementBuilders.Box;
 import androidx.wear.tiles.LayoutElementBuilders.LayoutElement;
 import androidx.wear.tiles.ModifiersBuilders.Clickable;
+import androidx.wear.tiles.ModifiersBuilders.ElementMetadata;
+import androidx.wear.tiles.ModifiersBuilders.Modifiers;
 import androidx.wear.tiles.proto.LayoutElementProto;
 
 /**
@@ -70,10 +75,13 @@
     /** Tool tag for Metadata in Modifiers, so we know that Box is actually a CompactChip. */
     static final String METADATA_TAG = "CMPCHP";
 
+    @NonNull private final Box mImpl;
     @NonNull private final Chip mElement;
 
-    CompactChip(@NonNull Chip element) {
-        this.mElement = element;
+    CompactChip(@NonNull Box element) {
+        this.mImpl = element;
+        // We know for sure that content of the Box is Chip.
+        this.mElement = new Chip((Box) element.getContents().get(0));
     }
 
     /** Builder class for {@link androidx.wear.tiles.material.CompactChip}. */
@@ -126,7 +134,7 @@
                             .setChipColors(mChipColors)
                             .setContentDescription(mText)
                             .setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
-                            .setWidth(new WrappedDimensionProp.Builder().build())
+                            .setWidth(wrap())
                             .setHeight(COMPACT_HEIGHT)
                             .setMaxLines(1)
                             .setHorizontalPadding(COMPACT_HORIZONTAL_PADDING)
@@ -134,7 +142,23 @@
                             .setPrimaryLabelTypography(Typography.TYPOGRAPHY_CAPTION1)
                             .setIsPrimaryLabelScalable(false);
 
-            return new CompactChip(chipBuilder.build());
+            Box tappableChip =
+                    new Box.Builder()
+                            .setModifiers(
+                                    new Modifiers.Builder()
+                                            .setClickable(mClickable)
+                                            .setMetadata(
+                                                    new ElementMetadata.Builder()
+                                                            .setTagData(getTagBytes(METADATA_TAG))
+                                                            .build())
+                                            .build())
+                            .setWidth(wrap())
+                            .setHeight(COMPACT_HEIGHT_TAPPABLE)
+                            .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
+                            .addContent(chipBuilder.build())
+                            .build();
+
+            return new CompactChip(tappableChip);
         }
     }
 
@@ -179,8 +203,18 @@
         if (!checkTag(boxElement.getModifiers(), METADATA_TAG)) {
             return null;
         }
+        // Now to check that inner content of the Box is CompactChip's Chip.
+        LayoutElement innerElement = boxElement.getContents().get(0);
+        if (!(innerElement instanceof Box)) {
+            return null;
+        }
+        Box innerBoxElement = (Box) innerElement;
+        if (!checkTag(innerBoxElement.getModifiers(), METADATA_TAG)) {
+            return null;
+        }
+
         // Now we are sure that this element is a CompactChip.
-        return new CompactChip(new Chip(boxElement));
+        return new CompactChip(boxElement);
     }
 
     /** @hide */
@@ -188,6 +222,6 @@
     @NonNull
     @Override
     public LayoutElementProto.LayoutElement toLayoutElementProto() {
-        return mElement.toLayoutElementProto();
+        return mImpl.toLayoutElementProto();
     }
 }
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java
index c56a119..b540215 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/LayoutDefaults.java
@@ -28,12 +28,12 @@
     /**
      * The default percentage for the bottom margin for primary chip in the {@link PrimaryLayout}.
      */
-    static final float PRIMARY_LAYOUT_MARGIN_BOTTOM_ROUND_PERCENT = 6.3f / 100;
+    static final float PRIMARY_LAYOUT_MARGIN_BOTTOM_ROUND_PERCENT = 2.1f / 100;
 
     /**
      * The default percentage for the bottom margin for primary chip in the {@link PrimaryLayout}.
      */
-    static final float PRIMARY_LAYOUT_MARGIN_BOTTOM_SQUARE_PERCENT = 2.2f / 100;
+    static final float PRIMARY_LAYOUT_MARGIN_BOTTOM_SQUARE_PERCENT = 0;
 
     /**
      * The default percentage for the top margin for primary chip in the {@link PrimaryLayout} on
@@ -71,9 +71,6 @@
      */
     static final float PRIMARY_LAYOUT_CHIP_HORIZONTAL_PADDING_SQUARE_DP = 0;
 
-    /** The default spacer height for primary chip in the {@link PrimaryLayout}. */
-    static final DpProp PRIMARY_LAYOUT_SPACER_HEIGHT = dp(12);
-
     /** The default horizontal margin in the {@link EdgeContentLayout}. */
     static final float EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_ROUND_DP = 14;
 
diff --git a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java
index f4e207c..228e91a 100644
--- a/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java
+++ b/wear/tiles/tiles-material/src/main/java/androidx/wear/tiles/material/layouts/PrimaryLayout.java
@@ -20,7 +20,7 @@
 import static androidx.wear.tiles.DimensionBuilders.dp;
 import static androidx.wear.tiles.DimensionBuilders.expand;
 import static androidx.wear.tiles.DimensionBuilders.wrap;
-import static androidx.wear.tiles.material.ChipDefaults.COMPACT_HEIGHT;
+import static androidx.wear.tiles.material.ChipDefaults.COMPACT_HEIGHT_TAPPABLE;
 import static androidx.wear.tiles.material.Helper.checkNotNull;
 import static androidx.wear.tiles.material.Helper.checkTag;
 import static androidx.wear.tiles.material.Helper.getMetadataTagBytes;
@@ -35,7 +35,6 @@
 import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_HORIZONTAL_SQUARE_PERCENT;
 import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_ROUND_PERCENT;
 import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT;
-import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_SPACER_HEIGHT;
 
 import android.annotation.SuppressLint;
 
@@ -235,10 +234,7 @@
             float horizontalPadding = getHorizontalPadding();
             float horizontalChipPadding = getChipHorizontalPadding();
 
-            float primaryChipHeight =
-                    mPrimaryChip != null
-                            ? (COMPACT_HEIGHT.getValue() + PRIMARY_LAYOUT_SPACER_HEIGHT.getValue())
-                            : 0;
+            float primaryChipHeight = mPrimaryChip != null ? COMPACT_HEIGHT_TAPPABLE.getValue() : 0;
 
             DpProp mainContentHeight =
                     dp(
@@ -292,31 +288,21 @@
             layoutBuilder.addContent(innerContentBuilder.build());
 
             if (mPrimaryChip != null) {
-                layoutBuilder
-                        .addContent(
-                                new Spacer.Builder()
-                                        .setHeight(PRIMARY_LAYOUT_SPACER_HEIGHT)
-                                        .build())
-                        .addContent(
-                                new Box.Builder()
-                                    .setVerticalAlignment(
-                                        LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM)
-                                    .setWidth(expand())
-                                    .setHeight(wrap())
-                                    .setModifiers(
+                layoutBuilder.addContent(
+                        new Box.Builder()
+                                .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM)
+                                .setWidth(expand())
+                                .setHeight(wrap())
+                                .setModifiers(
                                         new Modifiers.Builder()
-                                            .setPadding(
-                                                new Padding.Builder()
-                                                    .setStart(
-                                                        dp(
-                                                            horizontalChipPadding))
-                                                    .setEnd(
-                                                        dp(
-                                                            horizontalChipPadding))
-                                                    .build())
-                                            .build())
-                                        .addContent(mPrimaryChip)
-                                        .build());
+                                                .setPadding(
+                                                        new Padding.Builder()
+                                                                .setStart(dp(horizontalChipPadding))
+                                                                .setEnd(dp(horizontalChipPadding))
+                                                                .build())
+                                                .build())
+                                .addContent(mPrimaryChip)
+                                .build());
             }
 
             byte[] metadata = METADATA_TAG_BASE.clone();
@@ -424,7 +410,7 @@
     @Nullable
     public LayoutElement getPrimaryChipContent() {
         if (areElementsPresent(CHIP_PRESENT)) {
-            return ((Box) mAllContent.get(2)).getContents().get(0);
+            return ((Box) mAllContent.get(1)).getContents().get(0);
         }
         return null;
     }