Merge changes I1bc6361c,If19a4597 into androidx-main am: 154887a5f2

Original change: https://android-review.googlesource.com/c/platform/frameworks/support/+/2574550

Change-Id: I6dd2e5baa7f7197fc42bcf45198f0484d3edd219
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java
index 725d8af..c59fab0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java
@@ -66,4 +66,13 @@
         }
         return provider;
     }
+
+    /**
+     * Clear all {@link CameraConfigProvider} instances.
+     */
+    public static void clear() {
+        synchronized (LOCK) {
+            CAMERA_CONFIG_PROVIDERS.clear();
+        }
+    }
 }
diff --git a/camera/camera-extensions/api/current.txt b/camera/camera-extensions/api/current.txt
index 0db7e93..5c6e740 100644
--- a/camera/camera-extensions/api/current.txt
+++ b/camera/camera-extensions/api/current.txt
@@ -15,6 +15,7 @@
     method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
     method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
+    method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
   }
 
 }
diff --git a/camera/camera-extensions/api/restricted_current.txt b/camera/camera-extensions/api/restricted_current.txt
index 0db7e93..5c6e740 100644
--- a/camera/camera-extensions/api/restricted_current.txt
+++ b/camera/camera-extensions/api/restricted_current.txt
@@ -15,6 +15,7 @@
     method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
     method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
+    method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
   }
 
 }
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
index 94f0466..9d75cee 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
@@ -17,6 +17,8 @@
 package androidx.camera.extensions
 
 import android.hardware.camera2.CameraCharacteristics
+import android.util.Range
+import android.util.Size
 import androidx.annotation.NonNull
 import androidx.camera.camera2.interop.Camera2CameraInfo
 import androidx.camera.core.Camera
@@ -26,6 +28,7 @@
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.MutableStateObservable
 import androidx.camera.extensions.internal.ExtensionVersion
+import androidx.camera.extensions.internal.VendorExtender
 import androidx.camera.extensions.internal.Version
 import androidx.camera.extensions.internal.VersionName
 import androidx.camera.extensions.util.ExtensionsTestUtil
@@ -267,7 +270,42 @@
     }
 
     @Test
-    fun getEstimatedCaptureLatencyRangeThrowsException_whenExtensionAvailabilityIsNotAvailable() {
+    fun getEstimatedCaptureLatencyRange_returnValueFromExtender() {
+        extensionsManager = ExtensionsManager.getInstanceAsync(
+            context,
+            cameraProvider,
+        )[10000, TimeUnit.MILLISECONDS]
+
+        assumeTrue(
+            extensionsManager.extensionsAvailability
+                == ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
+        )
+        val estimatedCaptureLatency = Range(100L, 1000L)
+
+        val fakeVendorExtender = object : VendorExtender {
+            override fun isExtensionAvailable(
+                cameraId: String,
+                characteristicsMap: MutableMap<String, CameraCharacteristics>
+            ): Boolean {
+                return true
+            }
+
+            override fun getEstimatedCaptureLatencyRange(size: Size?): Range<Long>? {
+                return estimatedCaptureLatency
+            }
+        }
+        extensionsManager.setVendorExtenderFactory {
+            fakeVendorExtender
+        }
+
+        assertThat(extensionsManager.getEstimatedCaptureLatencyRange(
+            baseCameraSelector,
+            extensionMode)
+        ).isEqualTo(estimatedCaptureLatency)
+    }
+
+    @Test
+    fun getEstimatedCaptureLatencyRangeReturnNull_whenExtensionAvailabilityIsNotAvailable() {
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
             cameraProvider,
@@ -279,12 +317,10 @@
                 != ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
         )
 
-        assertThrows<IllegalArgumentException> {
-            extensionsManager.getEstimatedCaptureLatencyRange(
+        assertThat(extensionsManager.getEstimatedCaptureLatencyRange(
                 baseCameraSelector,
-                extensionMode
-            )
-        }
+                extensionMode)
+        ).isNull()
     }
 
     @Test
@@ -306,19 +342,19 @@
     }
 
     @Test
-    fun getEstimatedCaptureLatencyRangeThrowsException_whenNoCameraCanBeFound() {
+    fun getEstimatedCaptureLatencyRangeReturnsNull_whenNoCameraCanBeFound() {
         checkExtensionAvailabilityAndInit()
 
         val emptyCameraSelector = CameraSelector.Builder()
             .addCameraFilter { _ -> ArrayList<CameraInfo>() }
             .build()
 
-        assertThrows<IllegalArgumentException> {
+        assertThat(
             extensionsManager.getEstimatedCaptureLatencyRange(
                 emptyCameraSelector,
                 extensionMode
             )
-        }
+        ).isNull()
     }
 
     @Test
@@ -424,6 +460,107 @@
         }
     }
 
+    @Test
+    fun isImageAnalysisSupportedReturnsFalse_whenHasNoAnalysisSizes() {
+        extensionsManager = ExtensionsManager.getInstanceAsync(
+            context,
+            cameraProvider,
+        )[10000, TimeUnit.MILLISECONDS]
+
+        val fakeVendorExtender = object : VendorExtender {
+            override fun isExtensionAvailable(
+                cameraId: String,
+                characteristicsMap: MutableMap<String, CameraCharacteristics>
+            ): Boolean {
+                return true
+            }
+
+            override fun getSupportedYuvAnalysisResolutions(): Array<Size> {
+                return emptyArray()
+            }
+        }
+        extensionsManager.setVendorExtenderFactory {
+            fakeVendorExtender
+        }
+
+        assumeTrue(
+            extensionsManager.extensionsAvailability
+                == ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
+        )
+
+        assertThat(extensionsManager.isImageAnalysisSupported(
+            baseCameraSelector,
+            extensionMode)
+        ).isFalse()
+    }
+
+    @Test
+    fun isImageAnalysisSupportedReturnsTrue_whenHasAnalysisSizes() {
+        extensionsManager = ExtensionsManager.getInstanceAsync(
+            context,
+            cameraProvider,
+        )[10000, TimeUnit.MILLISECONDS]
+
+        val fakeVendorExtender = object : VendorExtender {
+            override fun isExtensionAvailable(
+                cameraId: String,
+                characteristicsMap: MutableMap<String, CameraCharacteristics>
+            ): Boolean {
+                return true
+            }
+
+            override fun getSupportedYuvAnalysisResolutions(): Array<Size> {
+                return arrayOf(Size(1920, 1080))
+            }
+        }
+        extensionsManager.setVendorExtenderFactory {
+            fakeVendorExtender
+        }
+
+        assumeTrue(
+            extensionsManager.extensionsAvailability
+                == ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
+        )
+
+        assertThat(extensionsManager.isImageAnalysisSupported(
+            baseCameraSelector,
+            extensionMode)
+        ).isTrue()
+    }
+    @Test
+    fun isImageAnalysisSupportedIsFalse_whenExtensionAvailabilityIsNotAvailable() {
+        extensionsManager = ExtensionsManager.getInstanceAsync(
+            context,
+            cameraProvider,
+            VersionName("99.0.0")
+        )[10000, TimeUnit.MILLISECONDS]
+
+        assumeTrue(
+            extensionsManager.extensionsAvailability
+                != ExtensionsManager.ExtensionsAvailability.LIBRARY_AVAILABLE
+        )
+
+        assertThat(extensionsManager.isImageAnalysisSupported(
+            baseCameraSelector,
+            extensionMode)
+        ).isFalse()
+    }
+
+    @Test
+    fun isImageAnalysisSupportedIsFalse_whenNoCameraCanBeFound() {
+        checkExtensionAvailabilityAndInit()
+        val emptyCameraSelector = CameraSelector.Builder()
+            .addCameraFilter { _ -> ArrayList<CameraInfo>() }
+            .build()
+
+        assertThat(
+            extensionsManager.isImageAnalysisSupported(
+                emptyCameraSelector,
+                extensionMode
+            )
+        ).isFalse()
+    }
+
     private fun checkExtensionAvailabilityAndInit(): CameraSelector {
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
@@ -444,21 +581,13 @@
     }
 
     private fun isExtensionAvailableByCameraInfo(cameraInfo: CameraInfo): Boolean {
-        val characteristics = Camera2CameraInfo.extractCameraCharacteristics(cameraInfo)
-        val cameraId = (cameraInfo as CameraInfoInternal).cameraId
-        val imageCaptureExtenderImpl =
-            ExtensionsTestUtil.createImageCaptureExtenderImpl(
-                extensionMode,
-                cameraId,
-                characteristics
-            )
-        val previewExtenderImpl =
-            ExtensionsTestUtil.createPreviewExtenderImpl(extensionMode, cameraId, characteristics)
+        var vendorExtender = ExtensionsTestUtil.createVendorExtender(extensionMode)
+        vendorExtender.init(cameraInfo)
+        val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo)
+        val cameraId = camera2CameraInfo.cameraId
 
-        return imageCaptureExtenderImpl.isExtensionAvailable(
-            cameraId,
-            characteristics
-        ) && previewExtenderImpl.isExtensionAvailable(cameraId, characteristics)
+        return vendorExtender.isExtensionAvailable(cameraId,
+            camera2CameraInfo.cameraCharacteristicsMap)
     }
 
     private fun createVideoCapture(): VideoCapture<TestVideoOutput> {
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
new file mode 100644
index 0000000..5ca9ee9
--- /dev/null
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2023 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.extensions
+
+import android.content.Context
+import android.graphics.ImageFormat
+import android.hardware.camera2.CameraCharacteristics
+import android.util.Pair
+import android.util.Size
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.extensions.internal.VendorExtender
+import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.SurfaceTextureProvider
+import androidx.camera.testing.fakes.FakeLifecycleOwner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = 21)
+class ImageAnalysisTest(
+    @ExtensionMode.Mode private val extensionMode: Int,
+    @CameraSelector.LensFacing private val lensFacing: Int
+) {
+    companion object {
+        @JvmStatic
+        @get:Parameterized.Parameters(name = "extension = {0}, facing = {1}")
+        val parameters: Collection<Array<Any>>
+            get() = ExtensionsTestUtil.getAllExtensionsLensFacingCombinations()
+    }
+
+    @get:Rule
+    val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+    )
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private lateinit var cameraProvider: ProcessCameraProvider
+    private lateinit var extensionsManager: ExtensionsManager
+    private lateinit var baseCameraSelector: CameraSelector
+    private lateinit var extensionsCameraSelector: CameraSelector
+    private lateinit var fakeLifecycleOwner: FakeLifecycleOwner
+    private lateinit var camera: Camera
+
+    @Before
+    fun setUp(): Unit = runBlocking {
+        Assume.assumeTrue(
+            ExtensionsTestUtil.isTargetDeviceAvailableForExtensions(
+                lensFacing,
+                extensionMode
+            )
+        )
+
+        cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
+        baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
+        extensionsManager = ExtensionsManager.getInstanceAsync(
+            context,
+            cameraProvider
+        )[10000, TimeUnit.MILLISECONDS]
+
+        Assume.assumeTrue(extensionsManager.isExtensionAvailable(baseCameraSelector, extensionMode))
+
+        withContext(Dispatchers.Main) {
+            fakeLifecycleOwner = FakeLifecycleOwner().apply { startAndResume() }
+            camera = withContext(Dispatchers.Main) {
+                cameraProvider.bindToLifecycle(fakeLifecycleOwner, baseCameraSelector)
+            }
+        }
+    }
+
+    @After
+    fun teardown(): Unit = runBlocking {
+        if (::cameraProvider.isInitialized) {
+            cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+        }
+
+        if (::extensionsManager.isInitialized) {
+            extensionsManager.shutdown()[10000, TimeUnit.MILLISECONDS]
+        }
+    }
+
+    @Test
+    fun canBindImageAnalysis_ifIsImageAnalysisSupportedReturnsTrue(): Unit = runBlocking {
+        // 1. Arrange
+        extensionsCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
+            baseCameraSelector,
+            extensionMode
+        )
+        Assume.assumeTrue(extensionsManager
+            .isImageAnalysisSupported(extensionsCameraSelector, extensionMode))
+
+        val analysisLatch = CountDownLatch(2)
+        withContext(Dispatchers.Main) {
+            val preview = Preview.Builder().build()
+            val imageCapture = ImageCapture.Builder().build()
+            val imageAnalysis = ImageAnalysis.Builder().build()
+
+            preview.setSurfaceProvider(
+                SurfaceTextureProvider.createSurfaceTextureProvider()
+            )
+
+            imageAnalysis.setAnalyzer(CameraXExecutors.ioExecutor()) {
+                analysisLatch.countDown()
+                it.close()
+            }
+
+            // 2. Act
+            cameraProvider.bindToLifecycle(
+                fakeLifecycleOwner,
+                extensionsCameraSelector,
+                preview, imageCapture, imageAnalysis
+            )
+        }
+
+        // 3. Assert
+        assertThat(analysisLatch.await(10000, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    private fun getOutputSizes(imageFormat: Int): Array<Size> {
+        val map = Camera2CameraInfo.from(camera.cameraInfo)
+            .getCameraCharacteristic(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
+        return map.getOutputSizes(imageFormat)
+    }
+
+    @Test
+    fun imageAnalysisResolutionIsFromVendorExtender(): Unit = runBlocking {
+        // 1. Arrange
+        val injectAnalysisSize = getOutputSizes(ImageFormat.YUV_420_888)
+            .minBy { it.width * it.height }
+        // Inject a fake VendorExtender that reports empty supported size for imageAnalysis.
+        extensionsManager.setVendorExtenderFactory {
+            object : VendorExtender {
+                override fun isExtensionAvailable(
+                    cameraId: String,
+                    characteristicsMap: MutableMap<String, CameraCharacteristics>
+                ) = true
+
+                override fun getSupportedYuvAnalysisResolutions(): Array<Size> {
+                    return arrayOf(injectAnalysisSize)
+                }
+
+                override fun getSupportedPreviewOutputResolutions(): List<Pair<Int, Array<Size>>> {
+                    return listOf(
+                        Pair(ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
+                            getOutputSizes(
+                                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE))
+                    )
+                }
+
+                override fun getSupportedCaptureOutputResolutions(): List<Pair<Int, Array<Size>>> {
+                    return listOf(
+                        Pair(ImageFormat.JPEG,
+                            getOutputSizes(ImageFormat.JPEG))
+                    )
+                }
+            }
+        }
+
+        extensionsCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
+            baseCameraSelector,
+            extensionMode
+        )
+        assertThat(extensionsManager
+            .isImageAnalysisSupported(baseCameraSelector, extensionMode)).isTrue()
+        withContext(Dispatchers.Main) {
+            val preview = Preview.Builder().build()
+            val imageCapture = ImageCapture.Builder().build()
+            val imageAnalysis = ImageAnalysis.Builder().build()
+
+            // 2. Act
+            cameraProvider.bindToLifecycle(
+                    fakeLifecycleOwner,
+                    extensionsCameraSelector,
+                    preview, imageCapture, imageAnalysis
+            )
+
+            // 3. Assert
+            assertThat(imageAnalysis.resolutionInfo!!.resolution).isEqualTo(injectAnalysisSize)
+        }
+    }
+
+    @Test
+    fun bindImageAnalysisThrowException_ifIsImageAnalysisSupportedReturnsFalse():
+        Unit = runBlocking {
+        // 1. Arrange
+        // Inject a fake VendorExtender that reports empty supported size for imageAnalysis.
+        extensionsManager.setVendorExtenderFactory {
+            object : VendorExtender {
+                override fun isExtensionAvailable(
+                    cameraId: String,
+                    characteristicsMap: MutableMap<String, CameraCharacteristics>
+                ) = true
+
+                override fun getSupportedYuvAnalysisResolutions(): Array<Size> {
+                    return emptyArray()
+                }
+
+                override fun getSupportedPreviewOutputResolutions(): List<Pair<Int, Array<Size>>> {
+                    return listOf(
+                        Pair(ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
+                            getOutputSizes(
+                                ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE))
+                    )
+                }
+
+                override fun getSupportedCaptureOutputResolutions(): List<Pair<Int, Array<Size>>> {
+                    return listOf(
+                        Pair(ImageFormat.JPEG,
+                            getOutputSizes(ImageFormat.JPEG))
+                    )
+                }
+            }
+        }
+
+        extensionsCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
+            baseCameraSelector,
+            extensionMode
+        )
+        assertThat(extensionsManager
+            .isImageAnalysisSupported(baseCameraSelector, extensionMode)).isFalse()
+        withContext(Dispatchers.Main) {
+            val preview = Preview.Builder().build()
+            val imageCapture = ImageCapture.Builder().build()
+            val imageAnalysis = ImageAnalysis.Builder().build()
+
+            // 3. Act && Assert
+            assertThrows<IllegalArgumentException> {
+                cameraProvider.bindToLifecycle(
+                    fakeLifecycleOwner,
+                    extensionsCameraSelector,
+                    preview, imageCapture, imageAnalysis
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
index 676b46a..0f50e2e 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/ExtensionsTestUtil.java
@@ -22,8 +22,6 @@
 import static androidx.camera.extensions.ExtensionMode.HDR;
 import static androidx.camera.extensions.ExtensionMode.NIGHT;
 
-import static junit.framework.TestCase.assertNotNull;
-
 import android.hardware.camera2.CameraCharacteristics;
 import android.os.Build;
 
@@ -31,18 +29,11 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.extensions.ExtensionMode;
-import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
-import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
-import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
-import androidx.camera.extensions.impl.BeautyPreviewExtenderImpl;
-import androidx.camera.extensions.impl.BokehImageCaptureExtenderImpl;
-import androidx.camera.extensions.impl.BokehPreviewExtenderImpl;
-import androidx.camera.extensions.impl.HdrImageCaptureExtenderImpl;
-import androidx.camera.extensions.impl.HdrPreviewExtenderImpl;
-import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
-import androidx.camera.extensions.impl.NightImageCaptureExtenderImpl;
-import androidx.camera.extensions.impl.NightPreviewExtenderImpl;
-import androidx.camera.extensions.impl.PreviewExtenderImpl;
+import androidx.camera.extensions.internal.AdvancedVendorExtender;
+import androidx.camera.extensions.internal.BasicVendorExtender;
+import androidx.camera.extensions.internal.ExtensionVersion;
+import androidx.camera.extensions.internal.VendorExtender;
+import androidx.camera.extensions.internal.Version;
 import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator;
 import androidx.camera.testing.CameraUtil;
 
@@ -71,84 +62,6 @@
     }
 
     /**
-     * Creates an {@link ImageCaptureExtenderImpl} object for specific {@link ExtensionMode} and
-     * camera id.
-     *
-     * @param extensionMode The extension mode for the created object.
-     * @param cameraId The target camera id.
-     * @param cameraCharacteristics The camera characteristics of the target camera.
-     * @return An {@link ImageCaptureExtenderImpl} object.
-     */
-    @NonNull
-    public static ImageCaptureExtenderImpl createImageCaptureExtenderImpl(
-            @ExtensionMode.Mode int extensionMode, @NonNull String cameraId,
-            @NonNull CameraCharacteristics cameraCharacteristics) {
-        ImageCaptureExtenderImpl impl = null;
-
-        switch (extensionMode) {
-            case HDR:
-                impl = new HdrImageCaptureExtenderImpl();
-                break;
-            case BOKEH:
-                impl = new BokehImageCaptureExtenderImpl();
-                break;
-            case FACE_RETOUCH:
-                impl = new BeautyImageCaptureExtenderImpl();
-                break;
-            case NIGHT:
-                impl = new NightImageCaptureExtenderImpl();
-                break;
-            case AUTO:
-                impl = new AutoImageCaptureExtenderImpl();
-                break;
-        }
-        assertNotNull(impl);
-
-        impl.init(cameraId, cameraCharacteristics);
-
-        return impl;
-    }
-
-    /**
-     * Creates a {@link PreviewExtenderImpl} object for specific {@link ExtensionMode} and
-     * camera id.
-     *
-     * @param extensionMode The extension mode for the created object.
-     * @param cameraId The target camera id.
-     * @param cameraCharacteristics The camera characteristics of the target camera.
-     * @return A {@link PreviewExtenderImpl} object.
-     */
-    @NonNull
-    public static PreviewExtenderImpl createPreviewExtenderImpl(
-            @ExtensionMode.Mode int extensionMode, @NonNull String cameraId,
-            @NonNull CameraCharacteristics cameraCharacteristics) {
-        PreviewExtenderImpl impl = null;
-
-        switch (extensionMode) {
-            case HDR:
-                impl = new HdrPreviewExtenderImpl();
-                break;
-            case BOKEH:
-                impl = new BokehPreviewExtenderImpl();
-                break;
-            case FACE_RETOUCH:
-                impl = new BeautyPreviewExtenderImpl();
-                break;
-            case NIGHT:
-                impl = new NightPreviewExtenderImpl();
-                break;
-            case AUTO:
-                impl = new AutoPreviewExtenderImpl();
-                break;
-        }
-        assertNotNull(impl);
-
-        impl.init(cameraId, cameraCharacteristics);
-
-        return impl;
-    }
-
-    /**
      * Returns whether the target camera device can support the test for a specific extension mode.
      */
     public static boolean isTargetDeviceAvailableForExtensions(
@@ -157,6 +70,20 @@
                 && !isSpecificSkippedDevice() && !isSpecificSkippedDeviceWithExtensionMode(mode);
     }
 
+    private static boolean isAdvancedExtenderSupported() {
+        if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_2) < 0) {
+            return false;
+        }
+        return ExtensionVersion.isAdvancedExtenderSupported();
+    }
+
+    public static VendorExtender createVendorExtender(@ExtensionMode.Mode int mode) {
+        if (isAdvancedExtenderSupported()) {
+            return new AdvancedVendorExtender(mode);
+        }
+        return new BasicVendorExtender(mode);
+    }
+
     /**
      * Returns whether the device is LIMITED hardware level above.
      *
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsInfo.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsInfo.java
index 912592b0..6ef8e616 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsInfo.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsInfo.java
@@ -25,6 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraFilter;
 import androidx.camera.core.CameraInfo;
@@ -60,9 +61,12 @@
     private static final String EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX = ":camera:camera"
             + "-extensions-";
     private final CameraProvider mCameraProvider;
+    @NonNull
+    private VendorExtenderFactory mVendorExtenderFactory;
 
     ExtensionsInfo(@NonNull CameraProvider cameraProvider) {
         mCameraProvider = cameraProvider;
+        mVendorExtenderFactory = (extensionMode) -> getVendorExtender(extensionMode);
     }
 
     /**
@@ -163,7 +167,8 @@
                 newCameraSelector.filter(mCameraProvider.getAvailableCameraInfos());
 
         if (cameraInfos.isEmpty()) {
-            throw new IllegalArgumentException("No cameras found for given CameraSelector");
+            // Returns null if the specified extension mode is not available.
+            return null;
         }
 
         extensionsCameraInfo = cameraInfos.get(0);
@@ -174,7 +179,7 @@
         }
 
         try {
-            VendorExtender vendorExtender = getVendorExtender(mode);
+            VendorExtender vendorExtender = mVendorExtenderFactory.createVendorExtender(mode);
             vendorExtender.init(extensionsCameraInfo);
 
             return vendorExtender.getEstimatedCaptureLatencyRange(resolution);
@@ -183,11 +188,36 @@
         }
     }
 
-    private static CameraFilter getFilter(@ExtensionMode.Mode int mode) {
+    boolean isImageAnalysisSupported(@NonNull CameraSelector cameraSelector,
+            @ExtensionMode.Mode int mode) {
+        CameraSelector newCameraSelector = CameraSelector.Builder.fromSelector(
+                cameraSelector).addCameraFilter(getFilter(mode)).build();
+        CameraInfo extensionsCameraInfo;
+        List<CameraInfo> cameraInfos =
+                newCameraSelector.filter(mCameraProvider.getAvailableCameraInfos());
+
+        if (cameraInfos.isEmpty()) {
+            // Returns false if the specified extension mode is not available on this camera.
+            return false;
+        }
+
+        extensionsCameraInfo = cameraInfos.get(0);
+        VendorExtender vendorExtender = mVendorExtenderFactory.createVendorExtender(mode);
+        vendorExtender.init(extensionsCameraInfo);
+        Size[] supportedYuvSizes = vendorExtender.getSupportedYuvAnalysisResolutions();
+        return supportedYuvSizes != null && supportedYuvSizes.length > 0;
+    }
+
+    @VisibleForTesting
+    void setVendorExtenderFactory(@NonNull VendorExtenderFactory factory) {
+        mVendorExtenderFactory = factory;
+    }
+
+    private CameraFilter getFilter(@ExtensionMode.Mode int mode) {
         CameraFilter filter;
         String id = getExtendedCameraConfigProviderId(mode);
 
-        VendorExtender vendorExtender = getVendorExtender(mode);
+        VendorExtender vendorExtender = mVendorExtenderFactory.createVendorExtender(mode);
         filter = new ExtensionCameraFilter(id, vendorExtender);
         return filter;
     }
@@ -196,12 +226,12 @@
      * Injects {@link CameraConfigProvider} for specified extension mode to the
      * {@link ExtendedCameraConfigProviderStore}.
      */
-    private static void injectExtensionCameraConfig(@ExtensionMode.Mode int mode) {
+    private void injectExtensionCameraConfig(@ExtensionMode.Mode int mode) {
         Identifier id = Identifier.create(getExtendedCameraConfigProviderId(mode));
 
         if (ExtendedCameraConfigProviderStore.getConfigProvider(id) == CameraConfigProvider.EMPTY) {
             ExtendedCameraConfigProviderStore.addConfig(id, (cameraInfo, context) -> {
-                VendorExtender vendorExtender = getVendorExtender(mode);
+                VendorExtender vendorExtender = mVendorExtenderFactory.createVendorExtender(mode);
                 vendorExtender.init(cameraInfo);
 
                 ExtensionsUseCaseConfigFactory factory = new
@@ -226,7 +256,7 @@
     }
 
     @NonNull
-    private static VendorExtender getVendorExtender(int mode) {
+    static VendorExtender getVendorExtender(@ExtensionMode.Mode int mode) {
         boolean isAdvancedExtenderSupported = isAdvancedExtenderSupported();
 
         VendorExtender vendorExtender;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
index b6c7ced..9020456 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
@@ -31,6 +31,7 @@
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Logger;
 import androidx.camera.core.Preview;
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
 import androidx.camera.core.impl.utils.ContextUtil;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.Futures;
@@ -312,6 +313,7 @@
 
             // Once extension has been initialized start the deinit call
             if (availability == ExtensionsAvailability.LIBRARY_AVAILABLE) {
+                ExtendedCameraConfigProviderStore.clear();
                 sDeinitializeFuture = CallbackToFutureAdapter.getFuture(completer -> {
                     try {
                         InitializerImpl.deinit(
@@ -423,29 +425,59 @@
      *                          specified extension mode.
      * @param mode              The extension mode to check.
      * @return the range of estimated minimal and maximal capture latency in milliseconds.
-     * Returns null if no capture latency info can be provided.
-     * @throws IllegalArgumentException If this device doesn't support extensions function, or no
-     *                                  camera can be found to support the specified extension mode.
+     * Returns null if no capture latency info can be provided or if the device doesn't support
+     * the extension mode on this camera.
      */
     @Nullable
     public Range<Long> getEstimatedCaptureLatencyRange(@NonNull CameraSelector cameraSelector,
             @ExtensionMode.Mode int mode) {
         if (mode == ExtensionMode.NONE
                 || mExtensionsAvailability != ExtensionsAvailability.LIBRARY_AVAILABLE) {
-            throw new IllegalArgumentException(
-                    "No camera can be found to support the specified extensions mode! "
-                            + "isExtensionAvailable should be checked first before calling "
-                            + "getEstimatedCaptureLatencyRange.");
+            // Returns null for non-Extensions mode or if Extensions are not supported on this
+            // device.
+            return null;
         }
 
         return mExtensionsInfo.getEstimatedCaptureLatencyRange(cameraSelector, mode, null);
     }
 
+    /**
+     * Returns whether the given extension mode supports the {@link ImageAnalysis} use case on
+     * the camera specified by the given {@link CameraSelector}. If it returns false, invoking
+     * {@code ProcessCameraProvider.bindToLifecycle} with an {@link ImageAnalysis} use case will
+     * throw an {@link IllegalArgumentException}.
+     *
+     * @param cameraSelector    The {@link CameraSelector} to find a camera which supports the
+     *                          specified extension mode.
+     * @param mode              The extension mode to check.
+     * @return true if {@link ImageAnalysis} can be bound when the specified extension mode is
+     * enabled on the camera specified by the given {@link CameraSelector}. Returns false
+     * otherwise. If the device doesn't support this extension mode on this camera, it will also
+     * return false.
+     */
+    public boolean isImageAnalysisSupported(@NonNull CameraSelector cameraSelector,
+            @ExtensionMode.Mode int mode) {
+        if (mode == ExtensionMode.NONE) {
+            return true;
+        }
+
+        // Returns false if Extensions are not supported on this device.
+        if (mExtensionsAvailability != ExtensionsAvailability.LIBRARY_AVAILABLE) {
+            return false;
+        }
+
+        return mExtensionsInfo.isImageAnalysisSupported(cameraSelector, mode);
+    }
+
     @VisibleForTesting
     @NonNull
     ExtensionsAvailability getExtensionsAvailability() {
         return mExtensionsAvailability;
     }
+    @VisibleForTesting
+    void setVendorExtenderFactory(VendorExtenderFactory vendorExtenderFactory) {
+        mExtensionsInfo.setVendorExtenderFactory(vendorExtenderFactory);
+    }
 
     private ExtensionsManager(@NonNull ExtensionsAvailability extensionsAvailability,
             @NonNull CameraProvider cameraProvider) {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/VendorExtenderFactory.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/VendorExtenderFactory.java
new file mode 100644
index 0000000..f8715c35
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/VendorExtenderFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 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.extensions;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.camera.extensions.internal.VendorExtender;
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+interface VendorExtenderFactory {
+    @NonNull
+    VendorExtender createVendorExtender(@ExtensionMode.Mode int extensionMode);
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
index 61f0550..d78bc97 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
@@ -35,6 +35,7 @@
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.Logger;
+import androidx.camera.core.impl.ImageFormatConstants;
 import androidx.camera.core.impl.SessionProcessor;
 import androidx.camera.extensions.ExtensionMode;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
@@ -63,8 +64,7 @@
 /**
  * Basic vendor interface implementation
  */
-@RequiresApi(23) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-// replaceImageFormatIfMissing accesses ImageFormat#PRIVATE which is public since API level 23.
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class BasicVendorExtender implements VendorExtender {
     private static final String TAG = "BasicVendorExtender";
     private final ExtensionDisabledValidator mExtensionDisabledValidator =
@@ -208,7 +208,7 @@
                 == PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
             return ImageFormat.YUV_420_888;
         } else {
-            return ImageFormat.PRIVATE;
+            return ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE /* PRIVATE */;
         }
     }
 
@@ -227,8 +227,10 @@
                     // PreviewExtenderImpl.getSupportedResolutions() returns the supported size
                     // for input surface. We need to ensure output surface format is supported.
                     return replaceImageFormatIfMissing(result,
-                            ImageFormat.YUV_420_888 /* formatToBeReplaced */,
-                            ImageFormat.PRIVATE /* newFormat */);
+                            /* formatToBeReplaced */
+                            ImageFormat.YUV_420_888,
+                            /* newFormat */
+                            ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE);
                 }
             } catch (NoSuchMethodError e) {
             }
@@ -241,7 +243,8 @@
         // able to output to the output surface, therefore we fetch the sizes from the
         // input image format for the output format.
         int inputImageFormat = getPreviewInputImageFormat();
-        return Arrays.asList(new Pair<>(ImageFormat.PRIVATE, getOutputSizes(inputImageFormat)));
+        return Arrays.asList(new Pair<>(ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
+                getOutputSizes(inputImageFormat)));
     }
 
 
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java
index 35f2a92..a3d601c 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java
@@ -18,16 +18,23 @@
 
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_ZSL_DISABLED;
 
+import android.graphics.ImageFormat;
+import android.util.Pair;
+import android.util.Size;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.ImageCapture.CaptureMode;
 import androidx.camera.core.impl.Config;
+import androidx.camera.core.impl.ImageAnalysisConfig;
 import androidx.camera.core.impl.MutableOptionsBundle;
 import androidx.camera.core.impl.OptionsBundle;
 import androidx.camera.core.impl.UseCaseConfigFactory;
 import androidx.camera.extensions.ExtensionMode;
 
+import java.util.List;
+
 /**
  * Implementation of UseCaseConfigFactory to provide the default extensions configurations for use
  * cases.
@@ -36,12 +43,33 @@
 public final class ExtensionsUseCaseConfigFactory implements UseCaseConfigFactory {
     private final ImageCaptureConfigProvider mImageCaptureConfigProvider;
     private final PreviewConfigProvider mPreviewConfigProvider;
+    private final ImageAnalysisConfigProvider mImageAnalysisConfigProvider;
 
     public ExtensionsUseCaseConfigFactory(
             @ExtensionMode.Mode int mode,
             @NonNull VendorExtender vendorExtender) {
         mImageCaptureConfigProvider = new ImageCaptureConfigProvider(mode, vendorExtender);
         mPreviewConfigProvider = new PreviewConfigProvider(mode, vendorExtender);
+        mImageAnalysisConfigProvider = new ImageAnalysisConfigProvider(vendorExtender);
+    }
+
+    private boolean isImageAnalysisSupported(
+            @Nullable List<Pair<Integer, Size[]>> supportedResolutions) {
+        if (supportedResolutions == null) {
+            return false;
+        }
+
+        for (Pair<Integer, Size[]> pair : supportedResolutions) {
+            int imageFormat = pair.first;
+            Size[] sizes = pair.second;
+            if (imageFormat == ImageFormat.YUV_420_888) {
+                if (sizes != null && sizes.length > 0) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
     }
 
     /**
@@ -65,6 +93,20 @@
                 mutableOptionsBundle =
                         MutableOptionsBundle.from(mPreviewConfigProvider.getConfig());
                 break;
+            case IMAGE_ANALYSIS: // invoked when ImageAnalysis is bound.
+                ImageAnalysisConfig config =  mImageAnalysisConfigProvider.getConfig();
+                List<Pair<Integer, Size[]>> supportedResolutions =
+                        config.getSupportedResolutions(/* valueIfMissing */ null);
+                if (!isImageAnalysisSupported(supportedResolutions)) {
+                    // This will be thrown when invoking bindToLifecycle.
+                    throw new IllegalArgumentException(
+                            "ImageAnalysis is not supported when Extension is enabled on "
+                                    + "this device. Check "
+                                    + "ExtensionsManager.isImageAnalysisSupported before binding "
+                                    + "the ImageAnalysis use case.");
+                }
+                mutableOptionsBundle = MutableOptionsBundle.from(config);
+                break;
             case VIDEO_CAPTURE:
                 throw new IllegalArgumentException("CameraX Extensions doesn't support "
                         + "VideoCapture!");
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ImageAnalysisConfigProvider.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ImageAnalysisConfigProvider.java
new file mode 100644
index 0000000..e2d6a64
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ImageAnalysisConfigProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 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.extensions.internal;
+
+import android.graphics.ImageFormat;
+import android.util.Pair;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.impl.ConfigProvider;
+import androidx.camera.core.impl.ImageAnalysisConfig;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides extensions related configs for image analysis
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ImageAnalysisConfigProvider implements ConfigProvider<ImageAnalysisConfig> {
+    private final VendorExtender mVendorExtender;
+    public ImageAnalysisConfigProvider(
+            @NonNull VendorExtender vendorExtender) {
+        mVendorExtender = vendorExtender;
+    }
+
+    @NonNull
+    @Override
+    public ImageAnalysisConfig getConfig() {
+        ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
+        Size[] sizes = mVendorExtender.getSupportedYuvAnalysisResolutions();
+        List<Pair<Integer, Size[]>> sizeList = new ArrayList<>();
+        if (sizes != null && sizes.length > 0) {
+            sizeList.add(new Pair<>(ImageFormat.YUV_420_888, sizes));
+        }
+        builder.setSupportedResolutions(sizeList);
+        return builder.getUseCaseConfig();
+    }
+}
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
index 85d64c1..d41cdc5 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
@@ -65,10 +65,12 @@
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.ImageAnalysis;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.MeteringPoint;
 import androidx.camera.core.Preview;
+import androidx.camera.core.UseCaseGroup;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.extensions.ExtensionMode;
 import androidx.camera.extensions.ExtensionsManager;
@@ -271,7 +273,23 @@
                 mCurrentCameraSelector, mCurrentExtensionMode);
 
         mCameraProvider.unbindAll();
-        mCamera = mCameraProvider.bindToLifecycle(this, cameraSelector, mImageCapture, mPreview);
+
+        UseCaseGroup.Builder useCaseGroupBuilder =
+                new UseCaseGroup.Builder()
+                        .addUseCase(mPreview)
+                        .addUseCase(mImageCapture);
+
+        if (mExtensionsManager.isImageAnalysisSupported(cameraSelector,
+                mCurrentExtensionMode)) {
+            ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
+            imageAnalysis.setAnalyzer(CameraXExecutors.ioExecutor(),  img -> {
+                img.close();
+            });
+            useCaseGroupBuilder.addUseCase(imageAnalysis);
+        }
+
+        mCamera = mCameraProvider.bindToLifecycle(this, cameraSelector,
+                useCaseGroupBuilder.build());
 
         // Update the UI and save location for ImageCapture
         Button toggleButton = findViewById(R.id.PhotoToggle);