Merge "Remove the camera-camera2 dependency in camera-extensions" into androidx-main
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
index 7b21869..024de86 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
@@ -103,6 +103,14 @@
     override fun getLensFacing(): Int =
         getCameraSelectorLensFacing(cameraProperties.metadata[CameraCharacteristics.LENS_FACING]!!)
 
+    override fun getCameraCharacteristics(): Any {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPhysicalCameraCharacteristics(physicalCameraId: String): Any? {
+        TODO("Not yet implemented")
+    }
+
     @CameraSelector.LensFacing
     private fun getCameraSelectorLensFacing(lensFacingInt: Int): Int {
         return when (lensFacingInt) {
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
index 420f747..72c5146 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
@@ -108,6 +108,14 @@
                 throw NotImplementedError("Not used in testing")
             }
 
+            override fun getCameraCharacteristics(): Any {
+                throw NotImplementedError("Not used in testing")
+            }
+
+            override fun getPhysicalCameraCharacteristics(physicalCameraId: String): Any? {
+                throw NotImplementedError("Not used in testing")
+            }
+
             override fun hasFlashUnit(): Boolean {
                 throw NotImplementedError("Not used in testing")
             }
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt
index fb74085..96e19cc 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2RequestProcessorTest.kt
@@ -28,7 +28,6 @@
 import android.os.Looper
 import android.view.Surface
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter
 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
 import androidx.camera.camera2.internal.compat.params.DynamicRangesCompat
 import androidx.camera.camera2.internal.compat.quirk.CameraQuirks
@@ -207,9 +206,8 @@
 
         // Assert
         withTimeout(5000) {
-            val (captureResult, receivedRequest) = callbackToVerify.awaitCaptureResults()[0]
-            val camera2CaptureResult =
-                Camera2CameraCaptureResultConverter.getCaptureResult(captureResult)!!
+            val (cameraCaptureResult, receivedRequest) = callbackToVerify.awaitCaptureResults()[0]
+            val camera2CaptureResult = cameraCaptureResult.captureResult
             assertThat(camera2CaptureResult.request.get(CaptureRequest.CONTROL_CAPTURE_INTENT))
                 .isEqualTo(CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE)
             assertThat(camera2CaptureResult.request.get(CaptureRequest.JPEG_ORIENTATION))
@@ -336,21 +334,19 @@
         // Assert
         withTimeout(5000) {
             val captureResults = callbackToVerify.awaitCaptureResults()
-            val (captureResult1, receivedRequest1) = captureResults[0]
-            val (captureResult2, receivedRequest2) = captureResults[1]
+            val (cameraCaptureResult1, receivedRequest1) = captureResults[0]
+            val (cameraCaptureResult2, receivedRequest2) = captureResults[1]
             captureImagesRetrieved[0].await()
             captureImagesRetrieved[1].await()
 
-            val camera2CaptureResult1 =
-                Camera2CameraCaptureResultConverter.getCaptureResult(captureResult1)!!
+            val camera2CaptureResult1 = cameraCaptureResult1.captureResult
             assertThat(camera2CaptureResult1.request.get(CaptureRequest.CONTROL_CAPTURE_INTENT))
                 .isEqualTo(CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE)
             assertThat(camera2CaptureResult1.request.get(CaptureRequest.JPEG_ORIENTATION))
                 .isEqualTo(ORIENTATION_1)
             assertThat(receivedRequest1).isSameInstanceAs(request1)
 
-            val camera2CaptureResult2 =
-                Camera2CameraCaptureResultConverter.getCaptureResult(captureResult2)!!
+            val camera2CaptureResult2 = cameraCaptureResult2.captureResult
             assertThat(camera2CaptureResult2.request.get(CaptureRequest.CONTROL_CAPTURE_INTENT))
                 .isEqualTo(CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW)
             assertThat(camera2CaptureResult2.request.get(CaptureRequest.JPEG_ORIENTATION))
@@ -401,10 +397,9 @@
 
         // Assert
         withTimeout(5000) {
-            val (captureResult, receivedRequest) = callbackToVerify.awaitCaptureResults()[0]
+            val (cameraCaptureResult, receivedRequest) = callbackToVerify.awaitCaptureResults()[0]
             previewImageRetrieved.await()
-            val camera2CaptureResult =
-                Camera2CameraCaptureResultConverter.getCaptureResult(captureResult)!!
+            val camera2CaptureResult = cameraCaptureResult.captureResult
             assertThat(camera2CaptureResult.request.get(CaptureRequest.CONTROL_CAPTURE_INTENT))
                 .isEqualTo(CaptureResult.CONTROL_CAPTURE_INTENT_PREVIEW)
             assertThat(camera2CaptureResult.request.get(CaptureRequest.JPEG_ORIENTATION))
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraCaptureResultConverter.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraCaptureResultConverter.java
deleted file mode 100644
index 7f884d0..0000000
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraCaptureResultConverter.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2019 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.camera2.impl;
-
-import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.CaptureResult;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.internal.Camera2CameraCaptureFailure;
-import androidx.camera.camera2.internal.Camera2CameraCaptureResult;
-import androidx.camera.core.impl.CameraCaptureFailure;
-import androidx.camera.core.impl.CameraCaptureResult;
-
-/**
-* An utility class to convert {@link CameraCaptureResult} to camera2 {@link CaptureResult}.
-*/
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public final class Camera2CameraCaptureResultConverter {
-    /**
-     * Converts {@link CameraCaptureResult} to camera2 {@link CaptureResult}.
-     *
-     * @return The CaptureResult instance or {@code null} if there is no underlying CaptureResult.
-     */
-    @Nullable
-    public static CaptureResult getCaptureResult(
-            @Nullable CameraCaptureResult cameraCaptureResult) {
-        if (cameraCaptureResult instanceof Camera2CameraCaptureResult) {
-            return ((Camera2CameraCaptureResult) cameraCaptureResult).getCaptureResult();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Converts {@link CameraCaptureFailure} to camera2 {@link CaptureFailure}.
-     *
-     * @return The CaptureFailure instance or {@code null} if there is no underlying CaptureFailure.
-     */
-    @Nullable
-    public static CaptureFailure getCaptureFailure(
-            @NonNull CameraCaptureFailure cameraCaptureFailure) {
-        if (cameraCaptureFailure instanceof Camera2CameraCaptureFailure) {
-            return ((Camera2CameraCaptureFailure) cameraCaptureFailure).getCaptureFailure();
-        } else {
-            return null;
-        }
-    }
-
-    private Camera2CameraCaptureResultConverter() {}
-}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraCaptureFailure.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraCaptureFailure.java
index d98e7b5..de5592c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraCaptureFailure.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraCaptureFailure.java
@@ -33,7 +33,8 @@
     }
 
     @NonNull
-    public CaptureFailure getCaptureFailure() {
+    @Override
+    public Object getCaptureFailure() {
         return mCaptureFailure;
     }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index 5dd2cf2..877cdca 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -555,6 +555,29 @@
         return mCamera2CameraInfo;
     }
 
+    @NonNull
+    @Override
+    public Object getCameraCharacteristics() {
+        return mCameraCharacteristicsCompat.toCameraCharacteristics();
+    }
+
+    @Nullable
+    @Override
+    public Object getPhysicalCameraCharacteristics(@NonNull String physicalCameraId) {
+        try {
+            if (!mCameraCharacteristicsCompat.getPhysicalCameraIds().contains(physicalCameraId)) {
+                return null;
+            }
+            return mCameraManager.getCameraCharacteristicsCompat(physicalCameraId)
+                    .toCameraCharacteristics();
+        } catch (CameraAccessExceptionCompat e) {
+            Logger.e(TAG,
+                    "Failed to get CameraCharacteristics for cameraId " + physicalCameraId,
+                    e);
+        }
+        return null;
+    }
+
     /**
      * Returns a map consisting of the camera ids and the {@link CameraCharacteristics}s.
      *
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2CameraCaptureResultConverterTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2CameraCaptureResultConverterTest.java
deleted file mode 100644
index 60d064b..0000000
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2CameraCaptureResultConverterTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2019 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.camera2.impl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.CaptureResult;
-import android.os.Build;
-
-import androidx.camera.camera2.internal.Camera2CameraCaptureFailure;
-import androidx.camera.camera2.internal.Camera2CameraCaptureResult;
-import androidx.camera.core.impl.CameraCaptureFailure;
-import androidx.camera.core.impl.CameraCaptureResult;
-import androidx.camera.core.impl.TagBundle;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public final class Camera2CameraCaptureResultConverterTest {
-    private CaptureResult mCaptureResult = Mockito.mock(CaptureResult.class);
-
-    @Test
-    public void canRetrieveCaptureResult() {
-        CameraCaptureResult cameraCaptureResult =
-                new Camera2CameraCaptureResult(TagBundle.emptyBundle(), mCaptureResult);
-
-        CaptureResult captureResult = Camera2CameraCaptureResultConverter.getCaptureResult(
-                cameraCaptureResult);
-
-        assertThat(captureResult).isSameInstanceAs(mCaptureResult);
-    }
-
-    @Test
-    public void retrieveNullIfNotCamera2CameraCaptureResult() {
-        CameraCaptureResult cameraCaptureResult =
-                new CameraCaptureResult.EmptyCameraCaptureResult();
-
-        CaptureResult captureResult = Camera2CameraCaptureResultConverter.getCaptureResult(
-                cameraCaptureResult);
-
-        assertThat(captureResult).isNull();
-    }
-
-    @Test
-    public void canRetrieveCaptureFailure() {
-        CaptureFailure captureFailure = Mockito.mock(CaptureFailure.class);
-        CameraCaptureFailure cameraCaptureFailure =
-                new Camera2CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR, captureFailure);
-
-        CaptureFailure retrievedCaptureResult =
-                Camera2CameraCaptureResultConverter.getCaptureFailure(
-                cameraCaptureFailure);
-
-        assertThat(retrievedCaptureResult).isSameInstanceAs(captureFailure);
-    }
-
-    @Test
-    public void retrieveNullIfNotCamera2CameraCaptureFailure() {
-        CameraCaptureFailure cameraCaptureFailure =
-                new CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR);
-
-        CaptureFailure captureFailure = Camera2CameraCaptureResultConverter.getCaptureFailure(
-                cameraCaptureFailure);
-
-        assertThat(captureFailure).isNull();
-    }
-}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
index 96f94bd..ff9a951 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
@@ -823,6 +823,53 @@
         assertThat(cameraInfo.querySupportedDynamicRanges(Collections.emptySet())).isEmpty();
     }
 
+    @Test
+    public void getCameraCharacteristics_returnCorrectValue() throws CameraAccessExceptionCompat {
+        init(/* hasAvailableCapabilities = */ true);
+
+        final CameraInfoInternal cameraInfo =
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
+
+        CameraCharacteristics cameraCharacteristic =
+                (CameraCharacteristics) cameraInfo.getCameraCharacteristics();
+        assertThat(cameraCharacteristic.get(CameraCharacteristics.SENSOR_ORIENTATION))
+                .isEqualTo(CAMERA0_SENSOR_ORIENTATION);
+    }
+
+    @Test
+    @Config(minSdk = 28)
+    public void getPhysicalCameraCharacteristicsByCameraId_returnCorrectValue()
+            throws CameraAccessExceptionCompat {
+        // Arrange: setup logical camera id "0" with physical camera ids ("2", "3") and
+        // camera id "4"
+        CameraCharacteristics characteristics0 = mock(CameraCharacteristics.class);
+        when(characteristics0.getPhysicalCameraIds()).thenReturn(
+                new HashSet<>(Arrays.asList("2", "3")));
+        CameraCharacteristics characteristics2 = mock(CameraCharacteristics.class);
+        CameraCharacteristics characteristics3 = mock(CameraCharacteristics.class);
+        CameraCharacteristics characteristics4 = mock(CameraCharacteristics.class);
+        ShadowCameraManager shadowCameraManager =
+                Shadow.extract(ApplicationProvider.getApplicationContext()
+                        .getSystemService(Context.CAMERA_SERVICE));
+        shadowCameraManager.addCamera("0", characteristics0);
+        shadowCameraManager.addCamera("2", characteristics2);
+        shadowCameraManager.addCamera("3", characteristics3);
+        shadowCameraManager.addCamera("4", characteristics4);
+
+        mCameraManagerCompat =
+                CameraManagerCompat.from((Context) ApplicationProvider.getApplicationContext());
+        final CameraInfoInternal cameraInfo = new Camera2CameraInfoImpl("0",
+                mCameraManagerCompat);
+
+        // Act / Assert:  Ensures getPhysicalCameraCharacteristics returns the correct instance
+        // for physical camera id "2" and "3" and null for id "4".
+        assertThat(cameraInfo.getPhysicalCameraCharacteristics("2"))
+                .isSameInstanceAs(characteristics2);
+        assertThat(cameraInfo.getPhysicalCameraCharacteristics("3"))
+                .isSameInstanceAs(characteristics3);
+        assertThat(cameraInfo.getPhysicalCameraCharacteristics("4")).isNull();
+    }
+
     private CameraManagerCompat initCameraManagerWithPhysicalIds(
             List> cameraIdsAndCharacteristicsList) {
         FakeCameraManagerImpl cameraManagerImpl = new FakeCameraManagerImpl();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraCaptureFailure.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraCaptureFailure.java
index af4b320..a5f1e04 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraCaptureFailure.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraCaptureFailure.java
@@ -17,6 +17,7 @@
 package androidx.camera.core.impl;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 /**
@@ -51,4 +52,13 @@
     public enum Reason {
         ERROR,
     }
+
+    /**
+     * Returns the capture failure object of the current implementation. In camera2 implementation,
+     * the object is of type {@link android.hardware.camera2.CaptureFailure}.
+     */
+    @Nullable
+    public Object getCaptureFailure() {
+        return null;
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
index 2e32da1..e0d1a5c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
@@ -22,6 +22,7 @@
 import android.util.Size;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
@@ -54,6 +55,26 @@
     String getCameraId();
 
     /**
+     * Returns the camera characteristics of this camera. The actual type is determined by the
+     * underlying camera implementation. For camera2 implementation, the actual type of the
+     * returned object is {@link android.hardware.camera2.CameraCharacteristics}.
+     */
+    @NonNull
+    Object getCameraCharacteristics();
+
+    /**
+     * Returns the camera characteristics of the specified physical camera id associated with
+     * the current camera.
+     *
+     * 

It returns {@code null} if the physical camera id does not belong to + * the current logical camera. The actual type is determined by the underlying camera + * implementation. For camera2 implementation, the actual type of the returned object is + * {@link android.hardware.camera2.CameraCharacteristics}. + */ + @Nullable + Object getPhysicalCameraCharacteristics(@NonNull String physicalCameraId); + + /** * Adds a {@link CameraCaptureCallback} which will be invoked when session capture request is * completed, failed or cancelled. *

diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
index fc397ff3..774ff31 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
@@ -20,6 +20,7 @@
 import android.util.Size;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraState;
@@ -209,4 +210,16 @@
     public boolean isVideoStabilizationSupported() {
         return mCameraInfoInternal.isVideoStabilizationSupported();
     }
+
+    @NonNull
+    @Override
+    public Object getCameraCharacteristics() {
+        return mCameraInfoInternal.getCameraCharacteristics();
+    }
+
+    @Nullable
+    @Override
+    public Object getPhysicalCameraCharacteristics(@NonNull String physicalCameraId) {
+        return mCameraInfoInternal.getPhysicalCameraCharacteristics(physicalCameraId);
+    }
 }
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index fbe84e3..bf8110aa 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -26,7 +26,6 @@
 dependencies {
     api(libs.guavaListenableFuture)
     api(project(":camera:camera-core"))
-    implementation(project(":camera:camera-camera2"))
     implementation("androidx.core:core:1.0.0")
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
     implementation(libs.autoValueAnnotations)
@@ -54,6 +53,7 @@
     androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has its own MockMaker
     androidTestImplementation(libs.truth)
     androidTestImplementation(libs.multidex)
+    androidTestImplementation(project(":camera:camera-camera2"))
     androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing")) {
         // Ensure camera-testing does not pull in androidx.test dependencies
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 5dc613d..136c0ac 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
@@ -20,7 +20,6 @@
 import android.util.Range
 import android.util.Size
 import androidx.annotation.NonNull
-import androidx.camera.camera2.interop.Camera2CameraInfo
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
@@ -30,6 +29,7 @@
 import androidx.camera.extensions.impl.ExtensionsTestlibControl
 import androidx.camera.extensions.internal.ClientVersion
 import androidx.camera.extensions.internal.ExtensionVersion
+import androidx.camera.extensions.internal.ExtensionsUtils
 import androidx.camera.extensions.internal.VendorExtender
 import androidx.camera.extensions.internal.Version
 import androidx.camera.extensions.util.ExtensionsTestUtil
@@ -207,8 +207,8 @@
         )
 
         for (cameraInfo in cameraProvider.availableCameraInfos) {
-            val characteristics = Camera2CameraInfo.extractCameraCharacteristics(cameraInfo)
-
+            val characteristics =
+                (cameraInfo as CameraInfoInternal).cameraCharacteristics as CameraCharacteristics
             // Checks lens facing first
             val currentLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
             if (currentLensFacing != lensFacing) {
@@ -236,7 +236,8 @@
         // Checks each camera in the available camera list that the selected camera must be the
         // first one supporting the specified extension mode in the same lens facing
         for (cameraInfo in cameraProvider.availableCameraInfos) {
-            val characteristics = Camera2CameraInfo.extractCameraCharacteristics(cameraInfo)
+            val characteristics =
+                (cameraInfo as CameraInfoInternal).cameraCharacteristics as CameraCharacteristics
 
             // Checks lens facing first
             val currentLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
@@ -246,7 +247,7 @@
 
             // Checks whether the specified extension mode is available by camera info
             val isSupported = isExtensionAvailableByCameraInfo(cameraInfo)
-            val currentCameraId = (cameraInfo as CameraInfoInternal).cameraId
+            val currentCameraId = cameraInfo.cameraId
 
             if (currentCameraId.equals(cameraId)) {
                 assertThat(isSupported).isTrue()
@@ -632,11 +633,10 @@
     private fun isExtensionAvailableByCameraInfo(cameraInfo: CameraInfo): Boolean {
         var vendorExtender = ExtensionsTestUtil.createVendorExtender(extensionMode)
         vendorExtender.init(cameraInfo)
-        val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo)
-        val cameraId = camera2CameraInfo.cameraId
+        val cameraId = (cameraInfo as CameraInfoInternal).cameraId
 
         return vendorExtender.isExtensionAvailable(cameraId,
-            camera2CameraInfo.cameraCharacteristicsMap)
+            ExtensionsUtils.getCameraCharacteristicsMap(cameraInfo))
     }
 
     private fun createVideoCapture(): VideoCapture {
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
index 935c0e1..c522674 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
@@ -22,12 +22,12 @@
 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.CameraInfoInternal
 import androidx.camera.core.impl.ImageFormatConstants
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.extensions.impl.ExtensionsTestlibControl
@@ -159,8 +159,10 @@
     }
 
     private fun getOutputSizes(imageFormat: Int): Array {
-        val map = Camera2CameraInfo.from(camera.cameraInfo)
-            .getCameraCharacteristic(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
+        val cameraCharacteristics =
+            (camera.cameraInfo as CameraInfoInternal).cameraCharacteristics as CameraCharacteristics
+        val map =
+            cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
         return map.getOutputSizes(imageFormat)
     }
 
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
index 3124f3b..29d78b9 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
@@ -25,6 +25,7 @@
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
 import android.hardware.camera2.TotalCaptureResult
+import android.hardware.camera2.params.OutputConfiguration
 import android.hardware.camera2.params.SessionConfiguration
 import android.media.ImageReader
 import android.media.ImageWriter
@@ -34,11 +35,6 @@
 import android.view.Surface
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.impl.Camera2ImplConfig
-import androidx.camera.camera2.internal.Camera2CameraInfoImpl
-import androidx.camera.camera2.internal.compat.CameraManagerCompat
-import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat
-import androidx.camera.camera2.interop.Camera2CameraInfo
 import androidx.camera.core.CameraFilter
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
@@ -49,6 +45,7 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.UseCaseGroup
 import androidx.camera.core.impl.CameraConfig
+import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
 import androidx.camera.core.impl.Identifier
@@ -67,6 +64,7 @@
 import androidx.camera.extensions.impl.advanced.SessionProcessorImpl
 import androidx.camera.extensions.internal.sessionprocessor.AdvancedSessionProcessor
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.SurfaceTextureProvider
 import androidx.camera.testing.impl.SurfaceTextureProvider.SurfaceTextureCallback
@@ -206,7 +204,7 @@
             CaptureRequest.JPEG_QUALITY to 0
         )
 
-        val config = Camera2ImplConfig.Builder().also {
+        val config = RequestOptionConfig.Builder().also {
             for (key in parametersMap.keys) {
                 @Suppress("UNCHECKED_CAST")
                 val anyKey = key as CaptureRequest.Key
@@ -237,15 +235,19 @@
             assertThat(realtimeCaptureLatencyEstimate?.second).isEqualTo(10L)
         }
 
+    @RequiresApi(28)
     private suspend fun assumeAllowsSharedSurface() = withContext(Dispatchers.Main) {
         val imageReader = ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, 2)
-        val maxSharedSurfaceCount =
-            OutputConfigurationCompat(imageReader.surface).maxSharedSurfaceCount
+        val outputConfiguration = OutputConfiguration(imageReader.surface)
+        val maxSharedSurfaceCount = outputConfiguration.maxSharedSurfaceCount
         imageReader.close()
 
         val camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector)
-        val hardwareLevel = Camera2CameraInfo.from(camera.cameraInfo)
-            .getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
+        val cameraCharacteristics = (camera.cameraInfo as CameraInfoInternal)
+            .cameraCharacteristics as CameraCharacteristics
+
+        val hardwareLevel = cameraCharacteristics
+            .get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
         assumeTrue(maxSharedSurfaceCount > 1 &&
             hardwareLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY)
     }
@@ -302,7 +304,7 @@
             return emptyList()
         }
         return CameraUtil.getPhysicalCameraIds(
-            Camera2CameraInfo.from(cameraInfos.get(0)).cameraId
+            (cameraInfos.get(0) as CameraInfoInternal).cameraId
         )
     }
 
@@ -392,7 +394,7 @@
         fakeSessionProcessImpl.sessionType = sessionTypeToVerify
         val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl,
             emptyList(), object : VendorExtender {}, context)
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val fakeCameraInfo = FakeCameraInfoInternal("0", context)
         val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
         val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
@@ -413,7 +415,7 @@
         fakeSessionProcessImpl.sessionType = -1
         val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl,
             emptyList(), object : VendorExtender {}, context)
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val fakeCameraInfo = FakeCameraInfoInternal("0", context)
         val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
         val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt
index 60c0b2e..41907044 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt
@@ -22,9 +22,9 @@
 import android.util.Pair
 import android.util.Size
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.interop.Camera2CameraInfo
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.ImageOutputConfig
 import androidx.camera.extensions.ExtensionMode
 import androidx.camera.extensions.impl.ImageCaptureExtenderImpl
@@ -143,8 +143,8 @@
 
     private fun generateImageCaptureSupportedResolutions(): List>> {
         val formatResolutionsPairList = mutableListOf>>()
-        val cameraInfo = cameraProvider.availableCameraInfos[0]
-        val characteristics = Camera2CameraInfo.extractCameraCharacteristics(cameraInfo)
+        val cameraInfo = cameraProvider.availableCameraInfos[0] as CameraInfoInternal
+        val characteristics = cameraInfo.cameraCharacteristics as CameraCharacteristics
         val map = characteristics[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
 
         // Retrieves originally supported resolutions from CameraCharacteristics for JPEG
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt
index 8dd6e2a..995cf91 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt
@@ -22,9 +22,9 @@
 import android.util.Pair
 import android.util.Size
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.interop.Camera2CameraInfo
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.Preview
+import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.ImageOutputConfig
 import androidx.camera.extensions.ExtensionMode
 import androidx.camera.extensions.impl.PreviewExtenderImpl
@@ -146,8 +146,8 @@
 
     private fun generatePreviewSupportedResolutions(): List>> {
         val formatResolutionsPairList = mutableListOf>>()
-        val cameraInfo = cameraProvider.availableCameraInfos[0]
-        val characteristics = Camera2CameraInfo.extractCameraCharacteristics(cameraInfo)
+        val cameraInfo = cameraProvider.availableCameraInfos[0] as CameraInfoInternal
+        val characteristics = cameraInfo.cameraCharacteristics as CameraCharacteristics
         val map = characteristics[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
 
         // Retrieves originally supported resolutions from CameraCharacteristics for PRIVATE
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
index a2757be..e02f1ca58 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
@@ -34,10 +34,6 @@
 import android.util.Size
 import android.view.Surface
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.internal.Camera2CameraInfoImpl
-import androidx.camera.camera2.internal.compat.CameraManagerCompat
-import androidx.camera.camera2.interop.Camera2CameraInfo
-import androidx.camera.camera2.interop.Camera2Interop
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraFilter
 import androidx.camera.core.CameraInfo
@@ -51,6 +47,7 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.UseCaseGroup
 import androidx.camera.core.impl.CameraConfig
+import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
 import androidx.camera.core.impl.Identifier
@@ -77,7 +74,9 @@
 import androidx.camera.extensions.internal.ClientVersion
 import androidx.camera.extensions.internal.ExtensionVersion
 import androidx.camera.extensions.internal.Version
+import androidx.camera.extensions.util.ExtensionsTestUtil
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import androidx.camera.testing.impl.CameraUtil
 import androidx.camera.testing.impl.SurfaceTextureProvider
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
@@ -178,7 +177,9 @@
         val camera = withContext(Dispatchers.Main) {
             cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector)
         }
-        val hardwareLevel = Camera2CameraInfo.from(camera.cameraInfo).getCameraCharacteristic(
+        val cameraCharacteristics =
+            (camera.cameraInfo as CameraInfoInternal).cameraCharacteristics as CameraCharacteristics
+        val hardwareLevel = cameraCharacteristics.get(
             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
         )
 
@@ -228,7 +229,7 @@
         fakeCaptureExtenderImpl.sessionType = sessionTypeToVerify
         fakePreviewExtenderImpl.sessionType = sessionTypeToVerify
 
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val fakeCameraInfo = FakeCameraInfoInternal("0", context)
         val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
         val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
@@ -247,7 +248,7 @@
         fakeCaptureExtenderImpl.sessionType = 2
         fakePreviewExtenderImpl.sessionType = 3
 
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val fakeCameraInfo = FakeCameraInfoInternal("0", context)
         val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
         val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
@@ -266,7 +267,7 @@
         fakeCaptureExtenderImpl.sessionType = -1
         fakePreviewExtenderImpl.sessionType = -1
 
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val fakeCameraInfo = FakeCameraInfoInternal("0", context)
         val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
         val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
@@ -460,8 +461,9 @@
     fun repeatingRequest_containsPreviewCaptureStagesParameters(): Unit = runBlocking {
         val previewBuilder = Preview.Builder()
         val resultMonitor = ResultMonitor()
-        Camera2Interop.Extender(previewBuilder)
-            .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() {
+        ExtensionsTestUtil.setCamera2SessionCaptureCallback(
+            previewBuilder,
+            object : CameraCaptureSession.CaptureCallback() {
                 override fun onCaptureCompleted(
                     session: CameraCaptureSession,
                     request: CaptureRequest,
@@ -502,8 +504,8 @@
         assumeTrue(previewProcessorType == PROCESSOR_TYPE_REQUEST_UPDATE_ONLY)
         val previewBuilder = Preview.Builder()
         val resultMonitor = ResultMonitor()
-        Camera2Interop.Extender(previewBuilder)
-            .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() {
+        ExtensionsTestUtil.setCamera2SessionCaptureCallback(previewBuilder,
+            object : CameraCaptureSession.CaptureCallback() {
                 override fun onCaptureCompleted(
                     session: CameraCaptureSession,
                     request: CaptureRequest,
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 8f92a27..5ff3f05 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
@@ -25,12 +25,15 @@
 import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.TESTLIB_ADVANCED;
 import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.TESTLIB_BASIC;
 
+import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.os.Build;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ExtendableBuilder;
+import androidx.camera.core.impl.Config;
 import androidx.camera.extensions.ExtensionMode;
 import androidx.camera.extensions.impl.ExtensionsTestlibControl;
 import androidx.camera.extensions.internal.AdvancedVendorExtender;
@@ -51,6 +54,10 @@
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class ExtensionsTestUtil {
+    public static final Config.Option
+            SESSION_CAPTURE_CALLBACK_OPTION =
+            Config.Option.create("camera2.cameraCaptureSession.captureCallback",
+                    CameraCaptureSession.CaptureCallback.class);
 
     /**
      * Returns the parameters which contains the combination of implementationType, extensions
@@ -181,4 +188,16 @@
     public static boolean extensionsDisabledByQuirk() {
         return new ExtensionDisabledValidator().shouldDisableExtension();
     }
+
+    /**
+     * Sets the camera2 repeating request capture callback to the use case builder.
+     */
+    public static  void setCamera2SessionCaptureCallback(
+            ExtendableBuilder usecaseBuilder,
+            @NonNull CameraCaptureSession.CaptureCallback captureCallback) {
+        usecaseBuilder.getMutableConfig().insertOption(
+                SESSION_CAPTURE_CALLBACK_OPTION,
+                captureCallback
+        );
+    }
 }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
index 0da3b2b..5ef69c3 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
@@ -19,14 +19,12 @@
 import android.hardware.camera2.CameraCharacteristics;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.interop.Camera2CameraInfo;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraFilter;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.Identifier;
+import androidx.camera.extensions.internal.ExtensionsUtils;
 import androidx.camera.extensions.internal.VendorExtender;
 import androidx.core.util.Preconditions;
 
@@ -54,7 +52,6 @@
         return mId;
     }
 
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @NonNull
     @Override
     public List filter(@NonNull List cameraInfos) {
@@ -62,11 +59,10 @@
         for (CameraInfo cameraInfo : cameraInfos) {
             Preconditions.checkArgument(cameraInfo instanceof CameraInfoInternal,
                     "The camera info doesn't contain internal implementation.");
-            String cameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
-
+            CameraInfoInternal cameraInfoInternal = (CameraInfoInternal) cameraInfo;
+            String cameraId = cameraInfoInternal.getCameraId();
             Map cameraCharacteristicsMap =
-                    Camera2CameraInfo.from(cameraInfo).getCameraCharacteristicsMap();
-
+                    ExtensionsUtils.getCameraCharacteristicsMap(cameraInfoInternal);
             if (mVendorExtender
                     .isExtensionAvailable(cameraId, cameraCharacteristicsMap)) {
                 result.add(cameraInfo);
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 0eda7fb..bc0d77d 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
@@ -23,10 +23,8 @@
 
 import androidx.annotation.NonNull;
 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;
 import androidx.camera.core.CameraProvider;
@@ -152,7 +150,6 @@
      *                                  extension mode.
      */
     @Nullable
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     Range getEstimatedCaptureLatencyRange(
             @NonNull CameraSelector cameraSelector,
             @ExtensionMode.Mode int mode, @Nullable Size resolution) {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
index c70750a..baa521e 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
@@ -26,13 +26,11 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
-import androidx.camera.camera2.interop.Camera2CameraInfo;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.Logger;
+import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.SessionProcessor;
 import androidx.camera.extensions.ExtensionMode;
 import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl;
@@ -97,13 +95,13 @@
         mMode = ExtensionMode.NONE;
     }
 
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @Override
     public void init(@NonNull CameraInfo cameraInfo) {
-        mCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
+        CameraInfoInternal cameraInfoInternal = (CameraInfoInternal) cameraInfo;
+        mCameraId = cameraInfoInternal.getCameraId();
 
         Map cameraCharacteristicsMap =
-                Camera2CameraInfo.from(cameraInfo).getCameraCharacteristicsMap();
+                ExtensionsUtils.getCameraCharacteristicsMap(cameraInfoInternal);
 
         mAdvancedExtenderImpl.init(mCameraId, cameraCharacteristicsMap);
     }
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 b170459..a07edc0 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
@@ -29,13 +29,11 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
-import androidx.camera.camera2.interop.Camera2CameraInfo;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.Logger;
+import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.ImageFormatConstants;
 import androidx.camera.core.impl.SessionProcessor;
 import androidx.camera.extensions.ExtensionMode;
@@ -75,7 +73,7 @@
             new ExtensionDisabledValidator();
     private PreviewExtenderImpl mPreviewExtenderImpl = null;
     private ImageCaptureExtenderImpl mImageCaptureExtenderImpl = null;
-    private CameraInfo mCameraInfo;
+    private CameraInfoInternal mCameraInfo;
     private String mCameraId;
     private CameraCharacteristics mCameraCharacteristics;
     private AvailableKeysRetriever mAvailableKeysRetriever = new AvailableKeysRetriever();
@@ -158,18 +156,16 @@
                 && mImageCaptureExtenderImpl.isExtensionAvailable(cameraId, cameraCharacteristics);
     }
 
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @Override
     public void init(@NonNull CameraInfo cameraInfo) {
-        mCameraInfo = cameraInfo;
+        mCameraInfo = (CameraInfoInternal) cameraInfo;
 
         if (mPreviewExtenderImpl == null || mImageCaptureExtenderImpl == null) {
             return;
         }
 
-        mCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
-        mCameraCharacteristics =
-                Camera2CameraInfo.extractCameraCharacteristics(cameraInfo);
+        mCameraId = mCameraInfo.getCameraId();
+        mCameraCharacteristics = (CameraCharacteristics) mCameraInfo.getCameraCharacteristics();
         mPreviewExtenderImpl.init(mCameraId, mCameraCharacteristics);
         mImageCaptureExtenderImpl.init(mCameraId, mCameraCharacteristics);
 
@@ -192,11 +188,9 @@
         return null;
     }
 
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     private Size[] getOutputSizes(int imageFormat) {
-        StreamConfigurationMap map = Camera2CameraInfo.from(mCameraInfo)
-                .getCameraCharacteristic(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-
+        StreamConfigurationMap map =
+                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
         return map.getOutputSizes(imageFormat);
     }
 
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/Camera2CameraCaptureResult.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/Camera2CameraCaptureResult.java
new file mode 100644
index 0000000..958324a
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/Camera2CameraCaptureResult.java
@@ -0,0 +1,299 @@
+/*
+ * 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.Rect;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.Logger;
+import androidx.camera.core.impl.CameraCaptureMetaData;
+import androidx.camera.core.impl.CameraCaptureResult;
+import androidx.camera.core.impl.TagBundle;
+import androidx.camera.core.impl.utils.ExifData;
+
+import java.nio.BufferUnderflowException;
+
+/**
+ * The camera2 implementation for the capture result of a single image capture.
+ *
+ * 

Copied from camera-camera2 since we don't want the camera-camera2 dependency but we need to + * generate the {@link CameraCaptureResult} from a + * {@link android.hardware.camera2.CaptureResult} instance. + */ +@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java +public class Camera2CameraCaptureResult implements CameraCaptureResult { + private static final String TAG = "C2CameraCaptureResult"; + + private final TagBundle mTagBundle; + + /** The actual camera2 {@link CaptureResult}. */ + private final CaptureResult mCaptureResult; + + public Camera2CameraCaptureResult(@NonNull TagBundle tagBundle, + @NonNull CaptureResult captureResult) { + mTagBundle = tagBundle; + mCaptureResult = captureResult; + } + + public Camera2CameraCaptureResult(@NonNull CaptureResult captureResult) { + this(TagBundle.emptyBundle(), captureResult); + } + + /** + * Converts the camera2 {@link CaptureResult#CONTROL_AF_MODE} to + * {@link CameraCaptureMetaData.AfMode}. + * + * @return the {@link CameraCaptureMetaData.AfMode}. + */ + @NonNull + @Override + public CameraCaptureMetaData.AfMode getAfMode() { + Integer mode = mCaptureResult.get(CaptureResult.CONTROL_AF_MODE); + if (mode == null) { + return CameraCaptureMetaData.AfMode.UNKNOWN; + } + switch (mode) { + case CaptureResult.CONTROL_AF_MODE_OFF: + case CaptureResult.CONTROL_AF_MODE_EDOF: + return CameraCaptureMetaData.AfMode.OFF; + case CaptureResult.CONTROL_AF_MODE_AUTO: + case CaptureResult.CONTROL_AF_MODE_MACRO: + return CameraCaptureMetaData.AfMode.ON_MANUAL_AUTO; + case CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE: + case CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO: + return CameraCaptureMetaData.AfMode.ON_CONTINUOUS_AUTO; + default: // fall out + } + Logger.e(TAG, "Undefined af mode: " + mode); + return CameraCaptureMetaData.AfMode.UNKNOWN; + } + + /** + * Converts the camera2 {@link CaptureResult#CONTROL_AF_STATE} to + * {@link CameraCaptureMetaData.AfState}. + * + * @return the {@link CameraCaptureMetaData.AfState}. + */ + @NonNull + @Override + public CameraCaptureMetaData.AfState getAfState() { + Integer state = mCaptureResult.get(CaptureResult.CONTROL_AF_STATE); + if (state == null) { + return CameraCaptureMetaData.AfState.UNKNOWN; + } + switch (state) { + case CaptureResult.CONTROL_AF_STATE_INACTIVE: + return CameraCaptureMetaData.AfState.INACTIVE; + case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN: + case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN: + return CameraCaptureMetaData.AfState.SCANNING; + case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: + return CameraCaptureMetaData.AfState.LOCKED_FOCUSED; + case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: + return CameraCaptureMetaData.AfState.LOCKED_NOT_FOCUSED; + case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: + return CameraCaptureMetaData.AfState.PASSIVE_NOT_FOCUSED; + case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: + return CameraCaptureMetaData.AfState.PASSIVE_FOCUSED; + + default: // fall out + } + Logger.e(TAG, "Undefined af state: " + state); + return CameraCaptureMetaData.AfState.UNKNOWN; + } + + /** + * Converts the camera2 {@link CaptureResult#CONTROL_AE_STATE} to + * {@link CameraCaptureMetaData.AeState}. + * + * @return the {@link CameraCaptureMetaData.AeState}. + */ + @NonNull + @Override + public CameraCaptureMetaData.AeState getAeState() { + Integer state = mCaptureResult.get(CaptureResult.CONTROL_AE_STATE); + if (state == null) { + return CameraCaptureMetaData.AeState.UNKNOWN; + } + switch (state) { + case CaptureResult.CONTROL_AE_STATE_INACTIVE: + return CameraCaptureMetaData.AeState.INACTIVE; + case CaptureResult.CONTROL_AE_STATE_SEARCHING: + case CaptureResult.CONTROL_AE_STATE_PRECAPTURE: + return CameraCaptureMetaData.AeState.SEARCHING; + case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: + return CameraCaptureMetaData.AeState.FLASH_REQUIRED; + case CaptureResult.CONTROL_AE_STATE_CONVERGED: + return CameraCaptureMetaData.AeState.CONVERGED; + case CaptureResult.CONTROL_AE_STATE_LOCKED: + return CameraCaptureMetaData.AeState.LOCKED; + default: // fall out + } + Logger.e(TAG, "Undefined ae state: " + state); + return CameraCaptureMetaData.AeState.UNKNOWN; + } + + /** + * Converts the camera2 {@link CaptureResult#CONTROL_AWB_STATE} to + * {@link CameraCaptureMetaData.AwbState}. + * + * @return the {@link CameraCaptureMetaData.AwbState}. + */ + @NonNull + @Override + public CameraCaptureMetaData.AwbState getAwbState() { + Integer state = mCaptureResult.get(CaptureResult.CONTROL_AWB_STATE); + if (state == null) { + return CameraCaptureMetaData.AwbState.UNKNOWN; + } + switch (state) { + case CaptureResult.CONTROL_AWB_STATE_INACTIVE: + return CameraCaptureMetaData.AwbState.INACTIVE; + case CaptureResult.CONTROL_AWB_STATE_SEARCHING: + return CameraCaptureMetaData.AwbState.METERING; + case CaptureResult.CONTROL_AWB_STATE_CONVERGED: + return CameraCaptureMetaData.AwbState.CONVERGED; + case CaptureResult.CONTROL_AWB_STATE_LOCKED: + return CameraCaptureMetaData.AwbState.LOCKED; + default: // fall out + } + Logger.e(TAG, "Undefined awb state: " + state); + return CameraCaptureMetaData.AwbState.UNKNOWN; + } + + /** + * Converts the camera2 {@link CaptureResult#FLASH_STATE} to + * {@link CameraCaptureMetaData.FlashState}. + * + * @return the {@link CameraCaptureMetaData.FlashState}. + */ + @NonNull + @Override + public CameraCaptureMetaData.FlashState getFlashState() { + Integer state = mCaptureResult.get(CaptureResult.FLASH_STATE); + if (state == null) { + return CameraCaptureMetaData.FlashState.UNKNOWN; + } + switch (state) { + case CaptureResult.FLASH_STATE_UNAVAILABLE: + case CaptureResult.FLASH_STATE_CHARGING: + return CameraCaptureMetaData.FlashState.NONE; + case CaptureResult.FLASH_STATE_READY: + return CameraCaptureMetaData.FlashState.READY; + case CaptureResult.FLASH_STATE_FIRED: + case CaptureResult.FLASH_STATE_PARTIAL: + return CameraCaptureMetaData.FlashState.FIRED; + default: // fall out + } + Logger.e(TAG, "Undefined flash state: " + state); + return CameraCaptureMetaData.FlashState.UNKNOWN; + } + + /** {@inheritDoc} */ + @Override + public long getTimestamp() { + Long timestamp = mCaptureResult.get(CaptureResult.SENSOR_TIMESTAMP); + if (timestamp == null) { + return -1L; + } + + return timestamp; + } + + @NonNull + @Override + public TagBundle getTagBundle() { + return mTagBundle; + } + + @Override + public void populateExifData(@NonNull ExifData.Builder exifData) { + // Call interface default to set flash mode + CameraCaptureResult.super.populateExifData(exifData); + + // Set dimensions + Rect cropRegion = mCaptureResult.get(CaptureResult.SCALER_CROP_REGION); + if (cropRegion != null) { + exifData.setImageWidth(cropRegion.width()) + .setImageHeight(cropRegion.height()); + } + + // Set orientation + try { + Integer jpegOrientation = mCaptureResult.get(CaptureResult.JPEG_ORIENTATION); + if (jpegOrientation != null) { + exifData.setOrientationDegrees(jpegOrientation); + } + } catch (BufferUnderflowException exception) { + // On certain devices, e.g. Pixel 3 XL API 31, getting JPEG orientation on YUV stream + // throws BufferUnderflowException. The value will be overridden in post-processing + // anyway, so it's safe to ignore. + Logger.w(TAG, "Failed to get JPEG orientation."); + } + + // Set exposure time + Long exposureTimeNs = mCaptureResult.get(CaptureResult.SENSOR_EXPOSURE_TIME); + if (exposureTimeNs != null) { + exifData.setExposureTimeNanos(exposureTimeNs); + } + + // Set the aperture + Float aperture = mCaptureResult.get(CaptureResult.LENS_APERTURE); + if (aperture != null) { + exifData.setLensFNumber(aperture); + } + + // Set the ISO + Integer iso = mCaptureResult.get(CaptureResult.SENSOR_SENSITIVITY); + if (iso != null) { + if (Build.VERSION.SDK_INT >= 24) { + Integer postRawSensitivityBoost = + mCaptureResult.get(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST); + if (postRawSensitivityBoost != null) { + iso *= (int) (postRawSensitivityBoost / 100f); + } + } + exifData.setIso(iso); + } + + // Set the focal length + Float focalLength = mCaptureResult.get(CaptureResult.LENS_FOCAL_LENGTH); + if (focalLength != null) { + exifData.setFocalLength(focalLength); + } + + // Set white balance MANUAL/AUTO + Integer whiteBalanceMode = mCaptureResult.get(CaptureResult.CONTROL_AWB_MODE); + if (whiteBalanceMode != null) { + ExifData.WhiteBalanceMode wbMode = ExifData.WhiteBalanceMode.AUTO; + if (whiteBalanceMode == CameraMetadata.CONTROL_AWB_MODE_OFF) { + wbMode = ExifData.WhiteBalanceMode.MANUAL; + } + exifData.setWhiteBalanceMode(wbMode); + } + } + + @NonNull + @Override + public CaptureResult getCaptureResult() { + return mCaptureResult; + } +}

diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUtils.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUtils.java
new file mode 100644
index 0000000..c7022ce
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUtils.java
@@ -0,0 +1,95 @@
+/*
+ * 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.hardware.camera2.CameraCharacteristics;
+import android.os.Build;
+
+import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.CameraInfoInternal;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Utils for camera-extensions.
+ */
+@RequiresApi(21)
+public class ExtensionsUtils {
+    private ExtensionsUtils() {}
+
+    /**
+     * Returns a map consisting of the camera ids and the {@link CameraCharacteristics}s.
+     *
+     * 

For every camera, the map contains at least the CameraCharacteristics for the camera id. + * If the camera is logical camera, it will also contain associated physical camera ids and + * their CameraCharacteristics. + * + */ + @NonNull + public static Map getCameraCharacteristicsMap( + @NonNull CameraInfoInternal cameraInfoInternal) { + LinkedHashMap map = new LinkedHashMap<>(); + String cameraId = cameraInfoInternal.getCameraId(); + CameraCharacteristics cameraCharacteristics = + (CameraCharacteristics) cameraInfoInternal.getCameraCharacteristics(); + map.put(cameraId, cameraCharacteristics); + + if (Build.VERSION.SDK_INT < 28) { + return map; + } + + Set physicalCameraIds = Api28Impl.getPhysicalCameraIds(cameraCharacteristics); + if (physicalCameraIds == null) { + return map; + } + + for (String physicalCameraId : physicalCameraIds) { + if (Objects.equals(physicalCameraId, cameraId)) { + continue; + } + map.put(physicalCameraId, (CameraCharacteristics) + cameraInfoInternal.getPhysicalCameraCharacteristics(physicalCameraId)); + } + return map; + } + + /** + * Nested class to avoid verification errors for methods introduced in API 28. + */ + @RequiresApi(28) + private static class Api28Impl { + + private Api28Impl() { + } + + @DoNotInline + static Set getPhysicalCameraIds( + @NonNull CameraCharacteristics cameraCharacteristics) { + try { + return cameraCharacteristics.getPhysicalCameraIds(); + } catch (Exception e) { + return Collections.emptySet(); + } + } + } +}

diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/RequestOptionConfig.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/RequestOptionConfig.java
new file mode 100644
index 0000000..6776335
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/RequestOptionConfig.java
@@ -0,0 +1,102 @@
+/*
+ * 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.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.impl.Config;
+import androidx.camera.core.impl.MutableOptionsBundle;
+import androidx.camera.core.impl.OptionsBundle;
+import androidx.camera.core.impl.ReadableConfig;
+
+/**
+ * Builder for creating {@link Config} that contains capture request options.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class RequestOptionConfig implements ReadableConfig {
+    static final String CAPTURE_REQUEST_ID_STEM = "camera2.captureRequest.option.";
+
+    @NonNull
+    private Config mConfig;
+
+    private RequestOptionConfig(@NonNull Config config) {
+        mConfig = config;
+    }
+
+    @NonNull
+    @Override
+    public Config getConfig() {
+        return mConfig;
+    }
+
+    @VisibleForTesting
+    @NonNull
+    static Option createOptionFromKey(@NonNull CaptureRequest.Key key) {
+        return Option.create(CAPTURE_REQUEST_ID_STEM + key.getName(),
+                Object.class,
+                key);
+    }
+
+    /**
+     * Builder for constructing {@link RequestOptionConfig} instances.
+     */
+    public static class Builder {
+        private MutableOptionsBundle mMutableOptionsBundle = MutableOptionsBundle.create();
+
+        /**
+         * Extract the capture request options from the given {@link Config} and create a
+         * {@link Builder} consisting of these capture request options.
+         */
+        @NonNull
+        public static Builder from(@NonNull Config config) {
+            Builder builder = new Builder();
+            config.findOptions(
+                    CAPTURE_REQUEST_ID_STEM,
+                    option -> {
+                        @SuppressWarnings("unchecked")
+                        Config.Option objectOpt = (Config.Option) option;
+                        builder.mMutableOptionsBundle.insertOption(objectOpt,
+                                config.getOptionPriority(objectOpt),
+                                config.retrieveOption(objectOpt));
+                        return true;
+                    });
+            return builder;
+        }
+
+        /**
+         * Sets the capture request option.
+         */
+        @NonNull
+        public  Builder setCaptureRequestOption(
+                @NonNull CaptureRequest.Key key, @NonNull ValueT value) {
+            Option option = createOptionFromKey(key);
+            mMutableOptionsBundle.insertOption(option, value);
+            return this;
+        }
+
+        /**
+         * Construct the instance.
+         */
+        @NonNull
+        public RequestOptionConfig build() {
+            return new RequestOptionConfig(OptionsBundle.from(mMutableOptionsBundle));
+        }
+    }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
index 587e4a2..433bf44 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
@@ -30,12 +30,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter;
-import androidx.camera.camera2.impl.Camera2ImplConfig;
-import androidx.camera.camera2.interop.CaptureRequestOptions;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.Logger;
 import androidx.camera.core.impl.CameraCaptureFailure;
 import androidx.camera.core.impl.CameraCaptureResult;
@@ -54,6 +49,7 @@
 import androidx.camera.extensions.impl.advanced.SessionProcessorImpl;
 import androidx.camera.extensions.internal.ClientVersion;
 import androidx.camera.extensions.internal.ExtensionVersion;
+import androidx.camera.extensions.internal.RequestOptionConfig;
 import androidx.camera.extensions.internal.VendorExtender;
 import androidx.camera.extensions.internal.Version;
 import androidx.core.util.Preconditions;
@@ -167,14 +163,13 @@
         mImpl.setParameters(map);
     }
 
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @NonNull
     private static HashMap, Object> convertConfigToMap(
             @NonNull Config parameters) {
         HashMap, Object> map = new HashMap<>();
 
-        CaptureRequestOptions options =
-                CaptureRequestOptions.Builder.from(parameters).build();
+        RequestOptionConfig options =
+                RequestOptionConfig.Builder.from(parameters).build();
 
         for (Config.Option option : options.listOptions()) {
             @SuppressWarnings("unchecked")
@@ -397,7 +392,6 @@
         private final int mTemplateId;
 
         @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-        @OptIn(markerClass = ExperimentalCamera2Interop.class)
         RequestAdapter(@NonNull RequestProcessorImpl.Request implRequest) {
             mImplRequest = implRequest;
 
@@ -407,15 +401,14 @@
             }
             mTargetOutputConfigIds = targetOutputConfigIds;
 
-            Camera2ImplConfig.Builder camera2ConfigBuilder = new Camera2ImplConfig.Builder();
+            RequestOptionConfig.Builder optionBuilder = new RequestOptionConfig.Builder();
             for (CaptureRequest.Key key : implRequest.getParameters().keySet()) {
                 @SuppressWarnings("unchecked")
                 CaptureRequest.Key objKey = (CaptureRequest.Key) key;
-                camera2ConfigBuilder.setCaptureRequestOption(objKey,
+                optionBuilder.setCaptureRequestOption(objKey,
                         implRequest.getParameters().get(objKey));
             }
-            mParameters = camera2ConfigBuilder.build();
-
+            mParameters = optionBuilder.build();
             mTemplateId = implRequest.getTemplateId();
         }
 
@@ -513,8 +506,7 @@
         public void onCaptureProgressed(
                 @NonNull RequestProcessor.Request request,
                 @NonNull CameraCaptureResult cameraCaptureResult) {
-            CaptureResult captureResult =
-                    Camera2CameraCaptureResultConverter.getCaptureResult(cameraCaptureResult);
+            CaptureResult captureResult = cameraCaptureResult.getCaptureResult();
             Preconditions.checkArgument(captureResult != null,
                     "Cannot get CaptureResult from the cameraCaptureResult ");
             mCallback.onCaptureProgressed(getImplRequest(request), captureResult);
@@ -524,8 +516,7 @@
         public void onCaptureCompleted(
                 @NonNull RequestProcessor.Request request,
                 @Nullable CameraCaptureResult cameraCaptureResult) {
-            CaptureResult captureResult =
-                    Camera2CameraCaptureResultConverter.getCaptureResult(cameraCaptureResult);
+            CaptureResult captureResult = cameraCaptureResult.getCaptureResult();
             Preconditions.checkArgument(captureResult instanceof TotalCaptureResult,
                     "CaptureResult in cameraCaptureResult is not a TotalCaptureResult");
             mCallback.onCaptureCompleted(getImplRequest(request),
@@ -536,11 +527,10 @@
         public void onCaptureFailed(
                 @NonNull RequestProcessor.Request request,
                 @Nullable CameraCaptureFailure cameraCaptureFailure) {
-            CaptureFailure captureFailure =
-                    Camera2CameraCaptureResultConverter.getCaptureFailure(cameraCaptureFailure);
-            Preconditions.checkArgument(captureFailure != null,
+            Object captureFailure = cameraCaptureFailure.getCaptureFailure();
+            Preconditions.checkArgument(captureFailure instanceof CaptureFailure,
                     "CameraCaptureFailure does not contain CaptureFailure.");
-            mCallback.onCaptureFailed(getImplRequest(request), captureFailure);
+            mCallback.onCaptureFailed(getImplRequest(request), (CaptureFailure) captureFailure);
         }
 
         @Override
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
index 953e2b6..81f64a8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
@@ -33,11 +33,7 @@
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter;
-import androidx.camera.camera2.interop.CaptureRequestOptions;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.Logger;
 import androidx.camera.core.impl.CameraCaptureFailure;
 import androidx.camera.core.impl.CameraCaptureResult;
@@ -54,6 +50,7 @@
 import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
 import androidx.camera.extensions.internal.ClientVersion;
 import androidx.camera.extensions.internal.ExtensionVersion;
+import androidx.camera.extensions.internal.RequestOptionConfig;
 import androidx.camera.extensions.internal.VendorExtender;
 import androidx.camera.extensions.internal.Version;
 import androidx.camera.extensions.internal.compat.workaround.OnEnableDisableSessionDurationCheck;
@@ -69,7 +66,6 @@
 /**
  * A {@link SessionProcessor} based on OEMs' basic extender implementation.
  */
-@OptIn(markerClass = ExperimentalCamera2Interop.class)
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class BasicExtenderSessionProcessor extends SessionProcessorBase {
     private static final String TAG = "BasicSessionProcessor";
@@ -257,8 +253,8 @@
         synchronized (mLock) {
             HashMap, Object> map = new HashMap<>();
 
-            CaptureRequestOptions options =
-                    CaptureRequestOptions.Builder.from(config).build();
+            RequestOptionConfig options =
+                    RequestOptionConfig.Builder.from(config).build();
 
             for (Config.Option option : options.listOptions()) {
                 @SuppressWarnings("unchecked")
@@ -437,9 +433,7 @@
             @Override
             public void onCaptureCompleted(@NonNull RequestProcessor.Request request,
                     @NonNull CameraCaptureResult cameraCaptureResult) {
-                CaptureResult captureResult =
-                        Camera2CameraCaptureResultConverter.getCaptureResult(
-                                cameraCaptureResult);
+                CaptureResult captureResult = cameraCaptureResult.getCaptureResult();
                 Preconditions.checkArgument(captureResult instanceof TotalCaptureResult,
                         "Cannot get TotalCaptureResult from the cameraCaptureResult ");
                 TotalCaptureResult totalCaptureResult = (TotalCaptureResult) captureResult;
@@ -543,9 +537,7 @@
             @Override
             public void onCaptureCompleted(@NonNull RequestProcessor.Request request,
                     @NonNull CameraCaptureResult cameraCaptureResult) {
-                CaptureResult captureResult =
-                        Camera2CameraCaptureResultConverter.getCaptureResult(
-                                cameraCaptureResult);
+                CaptureResult captureResult = cameraCaptureResult.getCaptureResult();
                 Preconditions.checkArgument(captureResult instanceof TotalCaptureResult,
                         "Cannot get capture TotalCaptureResult from the cameraCaptureResult ");
                 TotalCaptureResult totalCaptureResult = (TotalCaptureResult) captureResult;
@@ -653,8 +645,8 @@
         applyParameters(builder);
         applyPreviewStagesParameters(builder);
 
-        CaptureRequestOptions options =
-                CaptureRequestOptions.Builder.from(config).build();
+        RequestOptionConfig options =
+                RequestOptionConfig.Builder.from(config).build();
         for (Config.Option option : options.listOptions()) {
             @SuppressWarnings("unchecked")
             CaptureRequest.Key key = (CaptureRequest.Key) option.getToken();
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/RequestBuilder.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/RequestBuilder.java
index 32d17b9..295e69c 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/RequestBuilder.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/RequestBuilder.java
@@ -20,12 +20,10 @@
 import android.hardware.camera2.CaptureRequest;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2ImplConfig;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.RequestProcessor;
+import androidx.camera.extensions.internal.RequestOptionConfig;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -35,7 +33,6 @@
 /**
  * A builder for building {@link androidx.camera.core.impl.RequestProcessor.Request}.
  */
-@OptIn(markerClass = ExperimentalCamera2Interop.class)
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 class RequestBuilder {
     private List mTargetOutputConfigIds = new ArrayList<>();
@@ -91,14 +88,14 @@
             mTemplateId = templateId;
             mCaptureStageId = captureStageId;
 
-            Camera2ImplConfig.Builder camera2ConfigBuilder = new Camera2ImplConfig.Builder();
+            RequestOptionConfig.Builder requestOptionBuilder = new RequestOptionConfig.Builder();
             for (CaptureRequest.Key key : parameters.keySet()) {
                 @SuppressWarnings("unchecked")
                 CaptureRequest.Key objKey = (CaptureRequest.Key) key;
-                camera2ConfigBuilder.setCaptureRequestOption(objKey,
+                requestOptionBuilder.setCaptureRequestOption(objKey,
                         parameters.get(objKey));
             }
-            mParameterConfig = camera2ConfigBuilder.build();
+            mParameterConfig = requestOptionBuilder.build();
         }
 
         @Override
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
index 0f2a791..c2a9bbb 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
@@ -27,14 +27,11 @@
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2ImplConfig;
-import androidx.camera.camera2.interop.Camera2CameraInfo;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraXThreads;
 import androidx.camera.core.Logger;
+import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.OutputSurfaceConfiguration;
 import androidx.camera.core.impl.RestrictedCameraControl;
@@ -43,6 +40,8 @@
 import androidx.camera.core.impl.SessionProcessor;
 import androidx.camera.core.impl.SessionProcessorSurface;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.extensions.internal.ExtensionsUtils;
+import androidx.camera.extensions.internal.RequestOptionConfig;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -166,14 +165,13 @@
 
     @NonNull
     @Override
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     public final SessionConfig initSession(@NonNull CameraInfo cameraInfo,
             @NonNull OutputSurfaceConfiguration outputSurfaceConfiguration) {
-        Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.from(cameraInfo);
+        CameraInfoInternal cameraInfoInternal = (CameraInfoInternal) cameraInfo;
         Map characteristicsMap =
-                camera2CameraInfo.getCameraCharacteristicsMap();
+                ExtensionsUtils.getCameraCharacteristicsMap(cameraInfoInternal);
         Camera2SessionConfig camera2SessionConfig = initSessionInternal(
-                camera2CameraInfo.getCameraId(), characteristicsMap, outputSurfaceConfiguration);
+                cameraInfoInternal.getCameraId(), characteristicsMap, outputSurfaceConfiguration);
 
         SessionConfig.Builder sessionConfigBuilder = new SessionConfig.Builder();
         synchronized (mLock) {
@@ -203,7 +201,7 @@
             }
         }
 
-        Camera2ImplConfig.Builder camera2ConfigurationBuilder = new Camera2ImplConfig.Builder();
+        RequestOptionConfig.Builder camera2ConfigurationBuilder = new RequestOptionConfig.Builder();
         for (CaptureRequest.Key key : camera2SessionConfig.getSessionParameters().keySet()) {
             @SuppressWarnings("unchecked")
             CaptureRequest.Key objKey = (CaptureRequest.Key) key;
@@ -218,7 +216,7 @@
                 CameraXThreads.TAG + "extensions_image_reader");
         mImageReaderHandlerThread.start();
 
-        mCameraId = camera2CameraInfo.getCameraId();
+        mCameraId = cameraInfoInternal.getCameraId();
         Logger.d(TAG, "initSession: cameraId=" + mCameraId);
         return sessionConfigBuilder.build();
     }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java
index 639b172..62dce28 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessor.java
@@ -29,7 +29,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.internal.Camera2CameraCaptureResult;
 import androidx.camera.core.ImageProxy;
 import androidx.camera.core.ImageReaderProxys;
 import androidx.camera.core.Logger;
@@ -41,6 +40,7 @@
 import androidx.camera.core.internal.CameraCaptureResultImageInfo;
 import androidx.camera.extensions.impl.CaptureProcessorImpl;
 import androidx.camera.extensions.impl.ProcessResultImpl;
+import androidx.camera.extensions.internal.Camera2CameraCaptureResult;
 import androidx.camera.extensions.internal.ClientVersion;
 import androidx.camera.extensions.internal.ExtensionVersion;
 import androidx.camera.extensions.internal.Version;
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/ExtensionsUtilsTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/ExtensionsUtilsTest.kt
new file mode 100644
index 0000000..4ffb433
--- /dev/null
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/ExtensionsUtilsTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.content.Context
+import android.graphics.Rect
+import android.hardware.camera2.CameraCharacteristics
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowCameraManager
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(
+    minSdk = 28,
+    instrumentedPackages = arrayOf("androidx.camera.extensions.internal")
+)
+class ExtensionsUtilsTest {
+    companion object {
+        val ACTIVE_ARRAY_0 = Rect(0, 0, 1920, 1080)
+        val ACTIVE_ARRAY_2 = Rect(0, 0, 640, 480)
+        val ACTIVE_ARRAY_3 = Rect(0, 0, 320, 240)
+    }
+
+    @Test
+    fun canReturnCameraCharacteriticsMap() {
+        registerCameraCharacteristics(
+            "0", ACTIVE_ARRAY_0, physicalCameraId = setOf("0", "2", "3"))
+        registerCameraCharacteristics("2", ACTIVE_ARRAY_2)
+        registerCameraCharacteristics("3", ACTIVE_ARRAY_3)
+
+        val cameraInfo = FakeCameraInfoInternal("0", ApplicationProvider.getApplicationContext())
+
+        val characteristicsMap = ExtensionsUtils.getCameraCharacteristicsMap(cameraInfo)
+        assertThat(characteristicsMap.size).isEqualTo(3)
+        assertThat(characteristicsMap.get("0")!!
+            .get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE))
+            .isSameInstanceAs(ACTIVE_ARRAY_0)
+        assertThat(characteristicsMap.get("2")!!
+            .get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE))
+            .isSameInstanceAs(ACTIVE_ARRAY_2)
+        assertThat(characteristicsMap.get("3")!!
+            .get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE))
+            .isSameInstanceAs(ACTIVE_ARRAY_3)
+    }
+
+    private fun registerCameraCharacteristics(
+        cameraId: String,
+        activeArray: Rect,
+        physicalCameraId: Set? = null
+    ) {
+        val characteristics0 = Mockito.mock(
+            CameraCharacteristics::class.java
+        )
+        physicalCameraId?.let {
+            Mockito.`when`(characteristics0.physicalCameraIds)
+                .thenReturn(physicalCameraId)
+        }
+        Mockito.`when`(characteristics0.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE))
+            .thenReturn(activeArray)
+
+        // Add the camera to the camera service
+        val shadowCameraManager = Shadow.extract(
+            ApplicationProvider.getApplicationContext()
+                .getSystemService(Context.CAMERA_SERVICE)
+        ) as ShadowCameraManager
+
+        shadowCameraManager.addCamera(cameraId, characteristics0)
+    }
+}
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/RequestOptionConfigTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/RequestOptionConfigTest.kt
new file mode 100644
index 0000000..c7f4595
--- /dev/null
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/RequestOptionConfigTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.hardware.camera2.CaptureRequest
+import android.os.Build
+import androidx.camera.core.impl.Config.Option
+import androidx.camera.core.impl.MutableOptionsBundle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(
+    minSdk = Build.VERSION_CODES.LOLLIPOP,
+    instrumentedPackages = arrayOf("androidx.camera.extensions.internal")
+)
+class RequestOptionConfigTest {
+    @Test
+    fun canBuildWithCaptureRequestOptions() {
+        val config = RequestOptionConfig.Builder()
+            .setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE,
+                CaptureRequest.CONTROL_AF_MODE_AUTO)
+            .setCaptureRequestOption(CaptureRequest.JPEG_ORIENTATION, 90)
+            .build()
+
+        assertThat(config.listOptions().size).isEqualTo(2)
+        assertThat(config.retrieveOption(
+            RequestOptionConfig.createOptionFromKey(CaptureRequest.CONTROL_AF_MODE))
+        ).isEqualTo(CaptureRequest.CONTROL_AF_MODE_AUTO)
+        assertThat(config.retrieveOption(
+            RequestOptionConfig.createOptionFromKey(CaptureRequest.JPEG_ORIENTATION))
+        ).isEqualTo(90)
+    }
+
+    @Test
+    fun canBuildFromConfig() {
+        val mutableOptionConfig = MutableOptionsBundle.create()
+        mutableOptionConfig.insertOption(
+            Option.create("NonCaptureOption", String::class.java, null), "value1")
+        mutableOptionConfig.insertOption(
+            Option.create("NonCaptureOption2", Integer::class.java, null), 99)
+        mutableOptionConfig.insertOption(
+            RequestOptionConfig.createOptionFromKey(CaptureRequest.CONTROL_AF_MODE),
+            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+
+        val requestOptionConfig = RequestOptionConfig.Builder.from(mutableOptionConfig)
+            .setCaptureRequestOption(CaptureRequest.JPEG_ORIENTATION, 180)
+            .build()
+        assertThat(requestOptionConfig.listOptions().size).isEqualTo(2)
+        assertThat(requestOptionConfig.retrieveOption(
+            RequestOptionConfig.createOptionFromKey(CaptureRequest.CONTROL_AF_MODE))
+        ).isEqualTo(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+        assertThat(requestOptionConfig.retrieveOption(
+            RequestOptionConfig.createOptionFromKey(CaptureRequest.JPEG_ORIENTATION))
+        ).isEqualTo(180)
+    }
+ }
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
index ce25a89..942f4ee8 100644
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
@@ -26,8 +26,6 @@
 import android.util.Pair
 import android.util.Range
 import android.util.Size
-import androidx.camera.camera2.internal.Camera2CameraInfoImpl
-import androidx.camera.camera2.internal.compat.CameraManagerCompat
 import androidx.camera.core.impl.RestrictedCameraControl
 import androidx.camera.extensions.impl.CaptureStageImpl
 import androidx.camera.extensions.impl.ImageCaptureExtenderImpl
@@ -37,6 +35,7 @@
 import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
 import androidx.camera.extensions.impl.advanced.RequestProcessorImpl
 import androidx.camera.extensions.impl.advanced.SessionProcessorImpl
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assume.assumeTrue
@@ -127,7 +126,7 @@
             vendorExtender = AdvancedVendorExtender(fakeAdvancedExtenderImpl)
         }
 
-        val cameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val cameraInfo = FakeCameraInfoInternal("0", context)
         vendorExtender!!.init(cameraInfo)
         val sessionProcessor = vendorExtender.createSessionProcessor(context)!!
         assertThat(sessionProcessor.supportedCameraOperations)
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
index 5048cbb..02f5b88 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
@@ -18,6 +18,9 @@
 
 import static androidx.camera.core.DynamicRange.SDR;
 
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraManager;
 import android.util.Range;
 import android.util.Rational;
 import android.util.Size;
@@ -25,6 +28,7 @@
 
 import androidx.annotation.FloatRange;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.camera.core.CameraSelector;
@@ -102,29 +106,48 @@
 
     private Timebase mTimebase = Timebase.UPTIME;
 
+    @Nullable
+    private CameraManager mCameraManager;
+
     public FakeCameraInfoInternal() {
         this(/*sensorRotation=*/ 0, /*lensFacing=*/ CameraSelector.LENS_FACING_BACK);
     }
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public FakeCameraInfoInternal(@NonNull String cameraId,
+            @NonNull Context context) {
+        this(cameraId, 0, CameraSelector.LENS_FACING_BACK, context);
+    }
+
     public FakeCameraInfoInternal(@NonNull String cameraId) {
-        this(cameraId, 0, CameraSelector.LENS_FACING_BACK);
+        this(cameraId, 0, CameraSelector.LENS_FACING_BACK, null);
     }
 
     public FakeCameraInfoInternal(@NonNull String cameraId,
             @CameraSelector.LensFacing int lensFacing) {
-        this(cameraId, 0, lensFacing);
+        this(cameraId, 0, lensFacing, null);
     }
 
     public FakeCameraInfoInternal(int sensorRotation, @CameraSelector.LensFacing int lensFacing) {
-        this("0", sensorRotation, lensFacing);
+        this("0", sensorRotation, lensFacing, null);
     }
 
     public FakeCameraInfoInternal(@NonNull String cameraId, int sensorRotation,
             @CameraSelector.LensFacing int lensFacing) {
+        this(cameraId, sensorRotation, lensFacing, null);
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public FakeCameraInfoInternal(@NonNull String cameraId, int sensorRotation,
+            @CameraSelector.LensFacing int lensFacing,
+            @Nullable Context context) {
         mCameraId = cameraId;
         mSensorRotation = sensorRotation;
         mLensFacing = lensFacing;
         mZoomLiveData = new MutableLiveData<>(ImmutableZoomState.create(1.0f, 4.0f, 1.0f, 0.0f));
+        if (context != null) {
+            mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+        }
     }
 
     /**
@@ -390,6 +413,28 @@
         mSupportedDynamicRanges.addAll(dynamicRanges);
     }
 
+    @NonNull
+    @Override
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public Object getCameraCharacteristics() {
+        try {
+            return mCameraManager.getCameraCharacteristics(mCameraId);
+        } catch (CameraAccessException e) {
+            throw new IllegalStateException("can't get CameraCharacteristics", e);
+        }
+    }
+
+    @Nullable
+    @Override
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public Object getPhysicalCameraCharacteristics(@NonNull String physicalCameraId) {
+        try {
+            return mCameraManager.getCameraCharacteristics(physicalCameraId);
+        } catch (CameraAccessException e) {
+            throw new IllegalStateException("can't get CameraCharacteristics", e);
+        }
+    }
+
     @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
     static final class FakeExposureState implements ExposureState {
         private int mIndex = 0;