Cleanup CameraExtensionMetadata and bring it up to date.

- Add a dedicated FakeCameraExtensionMetadata implementation
- Add utility functions for Lazy value properties
- Remove Camera2 types from the CameraMetadataProvider
- Integrate FakeExtensionMetadata into FakeCameraMetadata
- Cleanup several TODOs
- Replaced several TODO() functions with explicit exceptions

Test: ./gradlew\
      :camera:camera-camera2-pipe:testDebugUnitTest\
      :camera:camera-camera2-pipe-testing:testDebugUnitTest\
      :camera:camera-camera2-pipe-integration:testDebugUnitTest

Change-Id: I641944263b06481243244d51665fb3213e8b4142
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt
index 359a02d..5c92fb2 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt
@@ -21,20 +21,11 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
-import android.hardware.camera2.TotalCaptureResult
-import android.view.Surface
+import android.util.Size
 import androidx.camera.camera2.pipe.CameraExtensionMetadata
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
-import androidx.camera.camera2.pipe.FrameInfo
-import androidx.camera.camera2.pipe.FrameMetadata
-import androidx.camera.camera2.pipe.FrameNumber
 import androidx.camera.camera2.pipe.Metadata
-import androidx.camera.camera2.pipe.Request
-import androidx.camera.camera2.pipe.RequestMetadata
-import androidx.camera.camera2.pipe.RequestNumber
-import androidx.camera.camera2.pipe.RequestTemplate
-import androidx.camera.camera2.pipe.StreamId
 import kotlin.reflect.KClass
 import kotlinx.atomicfu.atomic
 
@@ -43,15 +34,6 @@
 internal fun nextFakeCameraId(): CameraId =
     CameraId("FakeCamera-${fakeCameraIds.incrementAndGet()}")
 
-private val fakeRequestNumbers = atomic(0L)
-
-internal fun nextFakeRequestNumber(): RequestNumber =
-    RequestNumber(fakeRequestNumbers.incrementAndGet())
-
-private val fakeFrameNumbers = atomic(0L)
-
-internal fun nextFakeFrameNumber(): FrameNumber = FrameNumber(fakeFrameNumbers.incrementAndGet())
-
 /** Utility class for interacting with objects that require pre-populated Metadata. */
 open class FakeMetadata(private val metadata: Map<Metadata.Key<*>, Any?> = emptyMap()) : Metadata {
     companion object {
@@ -79,7 +61,7 @@
     override val sessionKeys: Set<CaptureRequest.Key<*>> = emptySet(),
     val physicalMetadata: Map<CameraId, CameraMetadata> = emptyMap(),
     override val physicalRequestKeys: Set<CaptureRequest.Key<*>> = emptySet(),
-    override val supportedExtensions: Set<Int> = emptySet(),
+    private val extensions: Map<Int, FakeCameraExtensionMetadata> = emptyMap(),
 ) : FakeMetadata(metadata), CameraMetadata {
 
     override fun <T> get(key: CameraCharacteristics.Key<T>): T? = characteristics[key] as T?
@@ -91,6 +73,8 @@
     override val isRedacted: Boolean = false
 
     override val physicalCameraIds: Set<CameraId> = physicalMetadata.keys
+    override val supportedExtensions: Set<Int>
+        get() = extensions.keys
 
     override suspend fun getPhysicalMetadata(cameraId: CameraId): CameraMetadata =
         physicalMetadata[cameraId]!!
@@ -99,11 +83,11 @@
         physicalMetadata[cameraId]!!
 
     override suspend fun getExtensionMetadata(extension: Int): CameraExtensionMetadata {
-        TODO("b/299356087 - Add support for fake extension metadata")
+        return extensions[extension]!!
     }
 
     override fun awaitExtensionMetadata(extension: Int): CameraExtensionMetadata {
-        TODO("b/299356087 - Add support for fake extension metadata")
+        return extensions[extension]!!
     }
 
     override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
@@ -111,81 +95,43 @@
     override fun toString(): String = "FakeCameraMetadata(camera: ${camera.value})"
 }
 
-/** Utility class for interacting with objects require specific [CaptureRequest] metadata. */
-class FakeRequestMetadata(
-    private val requestParameters: Map<CaptureRequest.Key<*>, Any?> = emptyMap(),
+/** Utility class for interacting with objects require [CameraExtensionMetadata] */
+class FakeCameraExtensionMetadata(
+    override val camera: CameraId,
+    override val cameraExtension: Int,
     metadata: Map<Metadata.Key<*>, Any?> = emptyMap(),
-    override val template: RequestTemplate = RequestTemplate(0),
-    override val streams: Map<StreamId, Surface> = mapOf(),
-    override val repeating: Boolean = false,
-    override val request: Request = Request(listOf()),
-    override val requestNumber: RequestNumber = nextFakeRequestNumber()
-) : FakeMetadata(request.extras.plus(metadata)), RequestMetadata {
-
-    override fun <T> get(key: CaptureRequest.Key<T>): T? = requestParameters[key] as T?
-
-    override fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T = get(key) ?: default
-
-    override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
-
-    companion object {
-        /** Initialize FakeRequestMetadata based on a specific [Request] object. */
-        fun from(
-            request: Request,
-            streamToSurfaces: Map<StreamId, Surface>,
-            repeating: Boolean = false
-        ): FakeRequestMetadata {
-            check(streamToSurfaces.keys.containsAll(request.streams))
-            return FakeRequestMetadata(
-                requestParameters = request.parameters,
-                template = request.template ?: RequestTemplate(0),
-                streams = request.streams.map { it to streamToSurfaces[it]!! }.toMap(),
-                repeating = repeating,
-                request = request
-            )
-        }
+    private val characteristics: Map<CameraCharacteristics.Key<*>, Any?> = emptyMap(),
+    override val requestKeys: Set<CaptureRequest.Key<*>> = emptySet(),
+    override val resultKeys: Set<CaptureResult.Key<*>> = emptySet(),
+    private val captureOutputSizes: Map<Int, Set<Size>> = emptyMap(),
+    private val previewOutputSizes: Map<Class<*>, Set<Size>> = emptyMap(),
+    private val postviewSizes: Map<Int, Map<Size, Set<Size>>> = emptyMap(),
+    override val isRedacted: Boolean = false,
+    override val isPostviewSupported: Boolean = false,
+    override val isCaptureProgressSupported: Boolean = false
+) : FakeMetadata(metadata), CameraExtensionMetadata {
+    override fun getOutputSizes(imageFormat: Int): Set<Size> {
+        return captureOutputSizes[imageFormat] ?: emptySet()
     }
 
-    override fun toString(): String =
-        "FakeRequestMetadata(requestNumber: ${requestNumber.value}, request: $request)"
-}
+    override fun getOutputSizes(klass: Class<*>): Set<Size> {
+        return previewOutputSizes[klass] ?: emptySet()
+    }
 
-/** Utility class for interacting with objects require specific [CaptureResult] metadata */
-class FakeFrameMetadata(
-    private val resultMetadata: Map<CaptureResult.Key<*>, Any?> = emptyMap(),
-    extraResultMetadata: Map<Metadata.Key<*>, Any?> = emptyMap(),
-    override val camera: CameraId = nextFakeCameraId(),
-    override val frameNumber: FrameNumber = nextFakeFrameNumber(),
-    override val extraMetadata: Map<*, Any?> = emptyMap<Any, Any>()
-) : FakeMetadata(extraResultMetadata), FrameMetadata {
+    override fun getPostviewSizes(captureSize: Size, format: Int): Set<Size> {
+        return postviewSizes[format]?.get(captureSize) ?: emptySet()
+    }
 
-    override fun <T> get(key: CaptureResult.Key<T>): T? =
-        extraMetadata[key] as T? ?: resultMetadata[key] as T?
+    override fun <T> get(key: CameraCharacteristics.Key<T>): T? = characteristics[key] as T?
 
-    override fun <T> getOrDefault(key: CaptureResult.Key<T>, default: T): T = get(key) ?: default
+    override fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T =
+        get(key) ?: default
+
+    override val keys: Set<CameraCharacteristics.Key<*>>
+        get() = characteristics.keys
 
     override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
 
     override fun toString(): String =
-        "FakeFrameMetadata(camera: ${camera.value}, frameNumber: ${frameNumber.value})"
-}
-
-/** Utility class for interacting with objects require specific [TotalCaptureResult] metadata */
-class FakeFrameInfo(
-    override val metadata: FrameMetadata = FakeFrameMetadata(),
-    override val requestMetadata: RequestMetadata = FakeRequestMetadata(),
-    private val physicalMetadata: Map<CameraId, FrameMetadata> = emptyMap()
-) : FrameInfo {
-    override fun get(camera: CameraId): FrameMetadata? = physicalMetadata[camera]
-
-    override val camera: CameraId
-        get() = metadata.camera
-
-    override val frameNumber: FrameNumber
-        get() = metadata.frameNumber
-
-    override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
-
-    override fun toString(): String =
-        "FakeFrameInfo(camera: ${camera.value}, frameNumber: ${frameNumber.value})"
+        "FakeCameraExtensionMetadata(camera: ${camera.value}, extension: $cameraExtension)"
 }
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeFrameMetadata.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeFrameMetadata.kt
new file mode 100644
index 0000000..0eac745
--- /dev/null
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeFrameMetadata.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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.pipe.testing
+
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.TotalCaptureResult
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.FrameInfo
+import androidx.camera.camera2.pipe.FrameMetadata
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Metadata
+import androidx.camera.camera2.pipe.RequestMetadata
+import kotlin.reflect.KClass
+import kotlinx.atomicfu.atomic
+
+private val fakeFrameNumbers = atomic(0L)
+
+internal fun nextFakeFrameNumber(): FrameNumber = FrameNumber(fakeFrameNumbers.incrementAndGet())
+
+/** Utility class for interacting with objects require specific [CaptureResult] metadata */
+class FakeFrameMetadata(
+    private val resultMetadata: Map<CaptureResult.Key<*>, Any?> = emptyMap(),
+    extraResultMetadata: Map<Metadata.Key<*>, Any?> = emptyMap(),
+    override val camera: CameraId = nextFakeCameraId(),
+    override val frameNumber: FrameNumber = nextFakeFrameNumber(),
+    override val extraMetadata: Map<*, Any?> = emptyMap<Any, Any>()
+) : FakeMetadata(extraResultMetadata), FrameMetadata {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T> get(key: CaptureResult.Key<T>): T? =
+        extraMetadata[key] as T? ?: resultMetadata[key] as T?
+
+    override fun <T> getOrDefault(key: CaptureResult.Key<T>, default: T): T = get(key) ?: default
+
+    override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
+
+    override fun toString(): String =
+        "FakeFrameMetadata(camera: ${camera.value}, frameNumber: ${frameNumber.value})"
+}
+
+/** Utility class for interacting with objects require specific [TotalCaptureResult] metadata */
+class FakeFrameInfo(
+    override val metadata: FrameMetadata = FakeFrameMetadata(),
+    override val requestMetadata: RequestMetadata = FakeRequestMetadata(),
+    private val physicalMetadata: Map<CameraId, FrameMetadata> = emptyMap()
+) : FrameInfo {
+    override fun get(camera: CameraId): FrameMetadata? = physicalMetadata[camera]
+
+    override val camera: CameraId
+        get() = metadata.camera
+
+    override val frameNumber: FrameNumber
+        get() = metadata.frameNumber
+
+    override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
+
+    override fun toString(): String =
+        "FakeFrameInfo(camera: ${camera.value}, frameNumber: ${frameNumber.value})"
+}
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestMetadata.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestMetadata.kt
new file mode 100644
index 0000000..d522939
--- /dev/null
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestMetadata.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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.pipe.testing
+
+import android.hardware.camera2.CaptureRequest
+import android.view.Surface
+import androidx.camera.camera2.pipe.Metadata
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.RequestNumber
+import androidx.camera.camera2.pipe.RequestTemplate
+import androidx.camera.camera2.pipe.StreamId
+import kotlin.reflect.KClass
+import kotlinx.atomicfu.atomic
+
+private val fakeRequestNumbers = atomic(0L)
+
+internal fun nextFakeRequestNumber(): RequestNumber =
+    RequestNumber(fakeRequestNumbers.incrementAndGet())
+
+/** Utility class for interacting with objects require specific [CaptureRequest] metadata. */
+class FakeRequestMetadata(
+    private val requestParameters: Map<CaptureRequest.Key<*>, Any?> = emptyMap(),
+    metadata: Map<Metadata.Key<*>, Any?> = emptyMap(),
+    override val template: RequestTemplate = RequestTemplate(0),
+    override val streams: Map<StreamId, Surface> = mapOf(),
+    override val repeating: Boolean = false,
+    override val request: Request = Request(listOf()),
+    override val requestNumber: RequestNumber = nextFakeRequestNumber()
+) : FakeMetadata(request.extras.plus(metadata)), RequestMetadata {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T> get(key: CaptureRequest.Key<T>): T? = requestParameters[key] as T?
+
+    override fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T = get(key) ?: default
+
+    override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
+
+    companion object {
+        /** Initialize FakeRequestMetadata based on a specific [Request] object. */
+        fun from(
+            request: Request,
+            streamToSurfaces: Map<StreamId, Surface>,
+            repeating: Boolean = false
+        ): FakeRequestMetadata {
+            check(streamToSurfaces.keys.containsAll(request.streams))
+            return FakeRequestMetadata(
+                requestParameters = request.parameters,
+                template = request.template ?: RequestTemplate(0),
+                streams = request.streams.map { it to streamToSurfaces[it]!! }.toMap(),
+                repeating = repeating,
+                request = request
+            )
+        }
+    }
+
+    override fun toString(): String =
+        "FakeRequestMetadata(requestNumber: ${requestNumber.value}, request: $request)"
+}
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
index f0cf2e8..fe5b689 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
@@ -58,7 +58,8 @@
     private val testScope = TestScope()
     private val metadata =
         FakeCameraMetadata(
-            mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT)
+            characteristics =
+                mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT)
         )
 
     private val streamConfig = CameraStream.Config.create(Size(640, 480), StreamFormat.YUV_420_888)
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraExtensionMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraExtensionMetadata.kt
index db923ec..160b1f3 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraExtensionMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraExtensionMetadata.kt
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-@file:RequiresApi(31) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-
 package androidx.camera.camera2.pipe
 
+import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraExtensionCharacteristics
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
 import android.util.Size
-import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 
 /**
@@ -37,17 +35,27 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 interface CameraExtensionMetadata : Metadata, UnsafeWrapper {
-    val camera: CameraId
-    val isRedacted: Boolean
-    val cameraExtension: Int
-    val isPostviewSupported: Boolean
+    operator fun <T> get(key: CameraCharacteristics.Key<T>): T?
 
+    fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T
+
+    val camera: CameraId
+    val cameraExtension: Int
+
+    val isRedacted: Boolean
+    val isPostviewSupported: Boolean
+    val isCaptureProgressSupported: Boolean
+
+    val keys: Set<CameraCharacteristics.Key<*>>
     val requestKeys: Set<CaptureRequest.Key<*>>
     val resultKeys: Set<CaptureResult.Key<*>>
 
+    /** Get output sizes that can be used for high-quality capture requests. */
     fun getOutputSizes(imageFormat: Int): Set<Size>
 
+    /** Get output sizes that can be used for repeating preview requests. */
     fun getOutputSizes(klass: Class<*>): Set<Size>
 
+    /** Get sizes that may be used for the postview stream. */
     fun getPostviewSizes(captureSize: Size, format: Int): Set<Size>
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt
index 781ba85..3ab4eb5 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ApiCompat.kt
@@ -497,6 +497,12 @@
     ): Boolean = extensionCharacteristics.isPostviewAvailable(extension)
 
     @JvmStatic
+    fun isCaptureProcessProgressAvailable(
+        extensionCharacteristics: CameraExtensionCharacteristics,
+        extension: Int
+    ): Boolean = extensionCharacteristics.isCaptureProcessProgressAvailable(extension)
+
+    @JvmStatic
     fun getPostviewSupportedSizes(
         extensionCharacteristics: CameraExtensionCharacteristics,
         extension: Int,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt
index 26b11b3..bb778e1 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraExtensionMetadata.kt
@@ -16,6 +16,7 @@
 
 package androidx.camera.camera2.pipe.compat
 
+import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraExtensionCharacteristics
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
@@ -26,8 +27,8 @@
 import androidx.camera.camera2.pipe.CameraExtensionMetadata
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.Metadata
-import androidx.camera.camera2.pipe.core.Debug
-import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.core.lazyOrEmptySet
+import androidx.camera.camera2.pipe.core.lazyOrFalse
 import kotlin.reflect.KClass
 
 /**
@@ -36,7 +37,6 @@
  * This allows all fields to be accessed and return reasonable values on all OS versions.
  */
 @RequiresApi(Build.VERSION_CODES.S)
-// TODO(b/200306659): Remove and replace with annotation on package-info.java
 internal class Camera2CameraExtensionMetadata(
     override val camera: CameraId,
     override val isRedacted: Boolean,
@@ -53,9 +53,16 @@
     @GuardedBy("supportedPostviewSizes")
     private val supportedPostviewSizes = mutableMapOf<Size, Lazy<Set<Size>>>()
 
-    // TODO: b/299356087 - this here may need a switch statement on the key
+    override fun <T> get(key: CameraCharacteristics.Key<T>): T? {
+        return null // TODO: Add support for this when VIC can be targeted in AndroidX
+    }
+
     @Suppress("UNCHECKED_CAST") override fun <T> get(key: Metadata.Key<T>): T? = metadata[key] as T?
 
+    override fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T {
+        return default // TODO: Add support for this when VIC can be targeted in AndroidX
+    }
+
     @Suppress("UNCHECKED_CAST")
     override fun <T> getOrDefault(key: Metadata.Key<T>, default: T): T =
         metadata[key] as T? ?: default
@@ -70,6 +77,12 @@
     override val isPostviewSupported: Boolean
         get() = _isPostviewSupported.value
 
+    override val isCaptureProgressSupported: Boolean
+        get() = _isCaptureProgressSupported.value
+
+    override val keys: Set<CameraCharacteristics.Key<*>>
+        get() = emptySet() // TODO: Add support for this when VIC can be targeted in AndroidX
+
     override val requestKeys: Set<CaptureRequest.Key<*>>
         get() = _requestKeys.value
 
@@ -77,10 +90,10 @@
         get() = _resultKeys.value
 
     override fun getOutputSizes(imageFormat: Int): Set<Size> {
-        val supportedExtensionSizes =
+        val lazySizes =
             synchronized(supportedExtensionSizesByFormat) {
                 supportedExtensionSizesByFormat.getOrPut(imageFormat) {
-                    lazy(LazyThreadSafetyMode.PUBLICATION) {
+                    lazyOrEmptySet({ "$camera#getExtensionSupportedSizes(${imageFormat})" }) {
                         Api31Compat.getExtensionSupportedSizes(
                                 extensionCharacteristics,
                                 cameraExtension,
@@ -90,14 +103,14 @@
                     }
                 }
             }
-        return supportedExtensionSizes.value
+        return lazySizes.value
     }
 
     override fun getOutputSizes(klass: Class<*>): Set<Size> {
-        val supportedExtensionSizes =
+        val lazySizes =
             synchronized(supportedExtensionSizesByClass) {
                 supportedExtensionSizesByClass.getOrPut(klass) {
-                    lazy(LazyThreadSafetyMode.PUBLICATION) {
+                    lazyOrEmptySet("$camera#getExtensionSupportedSizes(${klass.name})") {
                         Api31Compat.getExtensionSupportedSizes(
                                 extensionCharacteristics,
                                 cameraExtension,
@@ -107,82 +120,71 @@
                     }
                 }
             }
-        return supportedExtensionSizes.value
+        return lazySizes.value
     }
 
     override fun getPostviewSizes(captureSize: Size, format: Int): Set<Size> {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            val supportedPostviewSizes =
-                synchronized(supportedPostviewSizes) {
-                    supportedPostviewSizes.getOrPut(captureSize) {
-                        lazy(LazyThreadSafetyMode.PUBLICATION) {
-                            Api34Compat.getPostviewSupportedSizes(
-                                    extensionCharacteristics,
-                                    cameraExtension,
-                                    captureSize,
-                                    format
-                                )
-                                .toSet()
-                        }
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            return emptySet()
+        }
+
+        val lazySizes =
+            synchronized(supportedPostviewSizes) {
+                supportedPostviewSizes.getOrPut(captureSize) {
+                    lazyOrEmptySet("$camera#getPostviewSupportedSizes($captureSize, $format)") {
+                        Api34Compat.getPostviewSupportedSizes(
+                                extensionCharacteristics,
+                                cameraExtension,
+                                captureSize,
+                                format
+                            )
+                            .toSet()
                     }
                 }
-            return supportedPostviewSizes.value
-        }
-        return emptySet()
+            }
+        return lazySizes.value
     }
 
     private val _requestKeys: Lazy<Set<CaptureRequest.Key<*>>> =
-        lazy(LazyThreadSafetyMode.PUBLICATION) {
-            try {
-                Debug.trace("Camera-$camera#availableCaptureRequestKeys") {
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-                        Api33Compat.getAvailableCaptureRequestKeys(
-                                extensionCharacteristics,
-                                cameraExtension
-                            )
-                            .toSet()
-                    } else {
-                        emptySet()
-                    }
-                }
-            } catch (e: AssertionError) {
-                Log.warn(e) { "Failed to getAvailableCaptureRequestKeys from Camera-$camera" }
+        lazyOrEmptySet({ "$camera#availableCaptureRequestKeys" }) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                Api33Compat.getAvailableCaptureRequestKeys(
+                        extensionCharacteristics,
+                        cameraExtension
+                    )
+                    .toSet()
+            } else {
                 emptySet()
             }
         }
 
     private val _resultKeys: Lazy<Set<CaptureResult.Key<*>>> =
-        lazy(LazyThreadSafetyMode.PUBLICATION) {
-            try {
-                Debug.trace("Camera-$camera#availableCaptureResultKeys") {
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-                        Api33Compat.getAvailableCaptureResultKeys(
-                                extensionCharacteristics,
-                                cameraExtension
-                            )
-                            .toSet()
-                    } else {
-                        emptySet()
-                    }
-                }
-            } catch (e: AssertionError) {
-                Log.warn(e) { "Failed to getAvailableCaptureResultKeys from Camera-$camera" }
+        lazyOrEmptySet({ "$camera#availableCaptureResultKeys" }) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                Api33Compat.getAvailableCaptureResultKeys(extensionCharacteristics, cameraExtension)
+                    .toSet()
+            } else {
                 emptySet()
             }
         }
 
     private val _isPostviewSupported: Lazy<Boolean> =
-        lazy(LazyThreadSafetyMode.PUBLICATION) {
-            try {
-                Debug.trace("Camera-$camera#isPostviewSupported") {
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-                        Api34Compat.isPostviewAvailable(extensionCharacteristics, cameraExtension)
-                    } else {
-                        false
-                    }
-                }
-            } catch (e: AssertionError) {
-                Log.warn(e) { "Failed to get isPostviewSupported from Camera-$camera" }
+        lazyOrFalse({ "$camera#isPostviewSupported" }) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                Api34Compat.isPostviewAvailable(extensionCharacteristics, cameraExtension)
+            } else {
+                false
+            }
+        }
+
+    private val _isCaptureProgressSupported: Lazy<Boolean> =
+        lazyOrFalse({ "$camera#isCaptureProgressSupported" }) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                Api34Compat.isCaptureProcessProgressAvailable(
+                    extensionCharacteristics,
+                    cameraExtension
+                )
+            } else {
                 false
             }
         }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
index 5c82c66..eef6f5a 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
@@ -17,7 +17,6 @@
 package androidx.camera.camera2.pipe.compat
 
 import android.hardware.camera2.CameraCharacteristics
-import android.hardware.camera2.CameraExtensionCharacteristics
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
 import android.os.Build
@@ -130,10 +129,6 @@
         return metadataProvider.awaitCameraMetadata(cameraId)
     }
 
-    private fun getExtensionCharacteristics(): CameraExtensionCharacteristics {
-        return metadataProvider.getCameraExtensionCharacteristics(camera)
-    }
-
     override suspend fun getExtensionMetadata(extension: Int): CameraExtensionMetadata {
         val existing = synchronized(extensionCache) { extensionCache[extension] }
         return if (existing != null) {
@@ -160,12 +155,7 @@
         lazy(LazyThreadSafetyMode.PUBLICATION) {
             try {
                 Debug.trace("Camera-$camera#supportedExtensions") {
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-                        val extensionCharacteristics = getExtensionCharacteristics()
-                        Api31Compat.getSupportedExtensions(extensionCharacteristics).toSet()
-                    } else {
-                        emptySet()
-                    }
+                    metadataProvider.getSupportedCameraExtensions(camera)
                 }
             } catch (e: AssertionError) {
                 Log.warn(e) { "Failed to getSupportedExtensions from Camera-$camera" }
@@ -176,7 +166,10 @@
     private val _keys: Lazy<Set<CameraCharacteristics.Key<*>>> =
         lazy(LazyThreadSafetyMode.PUBLICATION) {
             try {
-                Debug.trace("$camera#keys") { characteristics.keys.orEmpty().toSet() }
+                Debug.trace("$camera#keys") {
+                    @Suppress("UselessCallOnNotNull") // Untrusted API
+                    characteristics.keys.orEmpty().toSet()
+                }
             } catch (e: AssertionError) {
                 Log.warn(e) { "Failed to getKeys from $camera}" }
                 emptySet()
@@ -187,6 +180,7 @@
         lazy(LazyThreadSafetyMode.PUBLICATION) {
             try {
                 Debug.trace("$camera#availableCaptureRequestKeys") {
+                    @Suppress("UselessCallOnNotNull") // Untrusted API
                     characteristics.availableCaptureRequestKeys.orEmpty().toSet()
                 }
             } catch (e: AssertionError) {
@@ -199,6 +193,7 @@
         lazy(LazyThreadSafetyMode.PUBLICATION) {
             try {
                 Debug.trace("$camera#availableCaptureResultKeys") {
+                    @Suppress("UselessCallOnNotNull") // Untrusted API
                     characteristics.availableCaptureResultKeys.orEmpty().toSet()
                 }
             } catch (e: AssertionError) {
@@ -271,7 +266,7 @@
             return this.get(key)
         } catch (exception: AssertionError) {
             throw IllegalStateException(
-                "Failed to get characteristic for $key: " + "Framework throw an AssertionError"
+                "Failed to get characteristic for $key: Framework throw an AssertionError"
             )
         }
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
index d32fad1..3ee3ee4 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
@@ -138,6 +138,14 @@
         }
     }
 
+    override fun getSupportedCameraExtensions(cameraId: CameraId): Set<Int> {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            val extensionCharacteristics = getCameraExtensionCharacteristics(cameraId)
+            return Api31Compat.getSupportedExtensions(extensionCharacteristics).toSet()
+        }
+        return emptySet()
+    }
+
     private fun createCameraMetadata(cameraId: CameraId, redacted: Boolean): Camera2CameraMetadata {
         val start = Timestamps.now(timeSource)
 
@@ -240,7 +248,7 @@
                 return@trace extensionMetadata
             } catch (throwable: Throwable) {
                 throw IllegalStateException(
-                    "Failed to load extension metadata " + "for $cameraId!",
+                    "Failed to load extension metadata for $cameraId!",
                     throwable
                 )
             }
@@ -248,7 +256,7 @@
     }
 
     @RequiresApi(Build.VERSION_CODES.S)
-    override fun getCameraExtensionCharacteristics(
+    private fun getCameraExtensionCharacteristics(
         cameraId: CameraId
     ): CameraExtensionCharacteristics {
         synchronized(extensionCharacteristicsCache) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataProvider.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataProvider.kt
index 9ebad5a..0968c3c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataProvider.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataProvider.kt
@@ -16,13 +16,13 @@
 
 package androidx.camera.camera2.pipe.compat
 
-import android.hardware.camera2.CameraExtensionCharacteristics
 import androidx.camera.camera2.pipe.CameraExtensionMetadata
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 
 /** Interface that can be used to query for [CameraMetadata] using an existing [CameraId]. */
 internal interface Camera2MetadataProvider {
+
     /** Attempt to retrieve [CameraMetadata], suspending the caller if it is not yet available. */
     suspend fun getCameraMetadata(cameraId: CameraId): CameraMetadata
 
@@ -31,9 +31,6 @@
      */
     fun awaitCameraMetadata(cameraId: CameraId): CameraMetadata
 
-    /** Attempt to retrieve [CameraExtensionCharacteristics] */
-    fun getCameraExtensionCharacteristics(cameraId: CameraId): CameraExtensionCharacteristics
-
     /**
      * Attempt to retrieve [CameraExtensionMetadata], blocking the calling thread if it is not yet
      * available.
@@ -48,4 +45,7 @@
      * available.
      */
     fun awaitCameraExtensionMetadata(cameraId: CameraId, extension: Int): CameraExtensionMetadata
+
+    /** Retrieve the set of supported Camera2 Extensions for the given camera. */
+    fun getSupportedCameraExtensions(cameraId: CameraId): Set<Int>
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionState.kt
index b44a7cb..6865b04 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionState.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionState.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:RequiresApi(31) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-
 package androidx.camera.camera2.pipe.compat
 
 import android.hardware.camera2.CameraCaptureSession
@@ -34,6 +32,7 @@
  * @param captureSessionState The [CaptureSessionState] instance to delegate the callback methods
  *   to.
  */
+@RequiresApi(31)
 internal class ExtensionSessionState(private val captureSessionState: CaptureSessionState) :
     CameraExtensionSessionWrapper.StateCallback {
     override fun onConfigured(session: CameraExtensionSessionWrapper) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt
index f3bc8756..0c86a32 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExtensionSessionWrapper.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:RequiresApi(31) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-
 package androidx.camera.camera2.pipe.compat
 
 import android.hardware.camera2.CameraCaptureSession
@@ -67,7 +65,7 @@
     }
 }
 
-@RequiresApi(31) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@RequiresApi(31)
 internal class AndroidExtensionSessionStateCallback(
     private val device: CameraDeviceWrapper,
     private val stateCallback: CameraExtensionSessionWrapper.StateCallback,
@@ -137,7 +135,7 @@
     }
 }
 
-@RequiresApi(31) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@RequiresApi(31)
 internal open class AndroidCameraExtensionSession(
     override val device: CameraDeviceWrapper,
     private val cameraExtensionSession: CameraExtensionSession,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
index 09dbcec..4035977 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/200306659): Remove and replace with annotation on package-info.java
 @file:Suppress("DEPRECATION")
 
 package androidx.camera.camera2.pipe.config
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
index 545cacb..a7471b8 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
@@ -44,7 +44,7 @@
      * @param label A name of the code section to appear in the trace.
      * @param block A block of code which is being traced.
      */
-    inline fun <T> trace(label: String, crossinline block: () -> T): T {
+    inline fun <T> trace(label: String, block: () -> T): T {
         try {
             traceStart { label }
             return block()
@@ -54,7 +54,7 @@
     }
 
     /** Wrap the specified [block] in a trace and timing calls. */
-    internal inline fun <T> instrument(label: String, crossinline block: () -> T): T {
+    internal inline fun <T> instrument(label: String, block: () -> T): T {
         val start = systemTimeSource.now()
         try {
             traceStart { label }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Lazy.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Lazy.kt
new file mode 100644
index 0000000..6de6a8d
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Lazy.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 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.pipe.core
+
+/**
+ * Utility function for creating inline [Lazy] instances that default to false if the block throws
+ * an exception while trying to read or compute the value.
+ */
+internal inline fun lazyOrFalse(
+    crossinline blockNameFn: () -> String,
+    crossinline block: () -> Boolean
+): Lazy<Boolean> =
+    lazy(LazyThreadSafetyMode.PUBLICATION) {
+        val blockName = blockNameFn()
+        try {
+            Debug.trace(blockName) { block() }
+        } catch (e: Throwable) {
+            Log.warn(e) { "Failed to get $blockName! Caching false and ignoring exception." }
+            false
+        }
+    }
+
+internal inline fun lazyOrFalse(
+    blockName: String,
+    crossinline block: () -> Boolean
+): Lazy<Boolean> = lazyOrFalse({ blockName }, block)
+
+/**
+ * Utility function for creating [Lazy] instances that default to an empty set if the block throws
+ * an exception while trying to read or compute the value.
+ */
+internal inline fun <T> lazyOrEmptySet(
+    crossinline blockNameFn: () -> String,
+    crossinline block: () -> Set<T>?
+): Lazy<Set<T>> =
+    lazy(LazyThreadSafetyMode.PUBLICATION) {
+        val blockName = blockNameFn()
+        try {
+            Debug.trace(blockName) { block() ?: emptySet() }
+        } catch (e: Throwable) {
+            Log.warn(e) { "Failed to get $blockName! Caching {} and ignoring exception." }
+            emptySet()
+        }
+    }
+
+internal inline fun <T> lazyOrEmptySet(
+    blockName: String,
+    crossinline block: () -> Set<T>?
+): Lazy<Set<T>> = lazyOrEmptySet({ blockName }, block)
+
+/**
+ * Utility function for creating [Lazy] instances that default to an empty list if the block throws
+ * an exception while trying to read or compute the value.
+ */
+internal inline fun <T> lazyOrEmptyList(
+    crossinline blockNameFn: () -> String,
+    crossinline block: () -> List<T>?
+): Lazy<List<T>> =
+    lazy(LazyThreadSafetyMode.PUBLICATION) {
+        val blockName = blockNameFn()
+        try {
+            Debug.trace(blockName) { block() ?: emptyList() }
+        } catch (e: Throwable) {
+            Log.warn(e) { "Failed to get $blockName! Caching [] and ignoring exception." }
+            emptyList()
+        }
+    }
+
+internal inline fun <T> lazyOrEmptyList(
+    blockName: String,
+    crossinline block: () -> List<T>
+): Lazy<List<T>> = lazyOrEmptyList({ blockName }, block)
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
index eceb3d8..70cf322 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.graphics.SurfaceTexture
-import android.hardware.camera2.CameraExtensionCharacteristics
 import android.os.Build
 import android.os.Looper
 import android.util.Size
@@ -55,7 +54,6 @@
 import dagger.Module
 import dagger.Provides
 import javax.inject.Singleton
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Test
@@ -66,7 +64,6 @@
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-@OptIn(ExperimentalCoroutinesApi::class)
 internal class CaptureSessionFactoryTest {
     private val context = ApplicationProvider.getApplicationContext() as Context
     private val mainLooper = Shadows.shadowOf(Looper.getMainLooper())
@@ -212,24 +209,22 @@
                 return fakeCamera.metadata
             }
 
-            override fun getCameraExtensionCharacteristics(
-                cameraId: CameraId
-            ): CameraExtensionCharacteristics {
-                TODO("b/299356087 - Add support for fake extension metadata")
-            }
-
             override suspend fun getCameraExtensionMetadata(
                 cameraId: CameraId,
                 extension: Int
             ): CameraExtensionMetadata {
-                TODO("b/299356087 - Add support for fake extension metadata")
+                throw UnsupportedOperationException("Unused for internal tests")
             }
 
             override fun awaitCameraExtensionMetadata(
                 cameraId: CameraId,
                 extension: Int
             ): CameraExtensionMetadata {
-                TODO("b/299356087 - Add support for fake extension metadata")
+                throw UnsupportedOperationException("Unused for internal tests")
+            }
+
+            override fun getSupportedCameraExtensions(cameraId: CameraId): Set<Int> {
+                throw UnsupportedOperationException("Unused for internal tests")
             }
         }
 }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt
index 7ce7123..dce35b4 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt
@@ -18,7 +18,6 @@
 
 import android.hardware.camera2.CameraAccessException
 import android.hardware.camera2.CameraDevice
-import android.hardware.camera2.CameraExtensionCharacteristics
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraError
 import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_CAMERA_DISABLED
@@ -70,24 +69,22 @@
             override fun awaitCameraMetadata(cameraId: CameraId): CameraMetadata =
                 FakeCameraMetadata(cameraId = cameraId)
 
-            override fun getCameraExtensionCharacteristics(
-                cameraId: CameraId
-            ): CameraExtensionCharacteristics {
-                TODO("b/299356087 - Add support for fake extension metadata")
-            }
-
             override suspend fun getCameraExtensionMetadata(
                 cameraId: CameraId,
                 extension: Int
             ): CameraExtensionMetadata {
-                TODO("b/299356087 - Add support for fake extension metadata")
+                throw UnsupportedOperationException("Not supported for this test")
             }
 
             override fun awaitCameraExtensionMetadata(
                 cameraId: CameraId,
                 extension: Int
             ): CameraExtensionMetadata {
-                TODO("b/299356087 - Add support for fake extension metadata")
+                throw UnsupportedOperationException("Not supported for this test")
+            }
+
+            override fun getSupportedCameraExtensions(cameraId: CameraId): Set<Int> {
+                throw UnsupportedOperationException("Not supported for this test")
             }
         }
 
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadataProvider.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2MetadataProvider.kt
similarity index 72%
rename from camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadataProvider.kt
rename to camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2MetadataProvider.kt
index 900c91c..f5dc394 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadataProvider.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2MetadataProvider.kt
@@ -16,16 +16,14 @@
 
 package androidx.camera.camera2.pipe.testing
 
-import android.hardware.camera2.CameraExtensionCharacteristics
 import androidx.camera.camera2.pipe.CameraExtensionMetadata
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.compat.Camera2MetadataProvider
 
 /** Utility class for providing fake metadata for tests. */
-class FakeCameraMetadataProvider(
-    private val fakeMetadata: Map<CameraId, CameraMetadata> = emptyMap(),
-    private val fakeExtensionMetadata: Map<CameraId, CameraExtensionMetadata> = emptyMap()
+class FakeCamera2MetadataProvider(
+    private val fakeMetadata: Map<CameraId, CameraMetadata> = emptyMap()
 ) : Camera2MetadataProvider {
     override suspend fun getCameraMetadata(cameraId: CameraId): CameraMetadata =
         awaitCameraMetadata(cameraId)
@@ -35,12 +33,6 @@
             "Failed to find metadata for $cameraId. Available fakeMetadata is $fakeMetadata"
         }
 
-    override fun getCameraExtensionCharacteristics(
-        cameraId: CameraId
-    ): CameraExtensionCharacteristics {
-        TODO("b/299356087 - Add support for fake extension metadata")
-    }
-
     override suspend fun getCameraExtensionMetadata(
         cameraId: CameraId,
         extension: Int
@@ -49,9 +41,8 @@
     override fun awaitCameraExtensionMetadata(
         cameraId: CameraId,
         extension: Int
-    ): CameraExtensionMetadata =
-        checkNotNull(fakeExtensionMetadata[cameraId]) {
-            "Failed to find extension metadata for $cameraId. Available " +
-                "fakeExtensionMetadata is $fakeExtensionMetadata"
-        }
+    ): CameraExtensionMetadata = awaitCameraMetadata(cameraId).awaitExtensionMetadata(extension)
+
+    override fun getSupportedCameraExtensions(cameraId: CameraId): Set<Int> =
+        awaitCameraMetadata(cameraId).supportedExtensions
 }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
index 65a5440..de2ec07 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
@@ -32,7 +32,7 @@
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.compat.Camera2CameraMetadata
 import androidx.test.core.app.ApplicationProvider
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
 import kotlinx.atomicfu.atomic
 import org.junit.After
 import org.junit.Test
@@ -104,7 +104,7 @@
                 cameraId,
                 false,
                 characteristics,
-                FakeCameraMetadataProvider(),
+                FakeCamera2MetadataProvider(),
                 emptyMap(),
                 emptySet()
             )
@@ -164,7 +164,6 @@
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class RobolectricCamerasTest {
-    private val context = ApplicationProvider.getApplicationContext() as Context
     private val mainLooper = shadowOf(Looper.getMainLooper())
 
     @Test
@@ -175,13 +174,13 @@
             )
         val fakeCamera = RobolectricCameras.open(fakeCameraId)
 
-        Truth.assertThat(fakeCamera).isNotNull()
-        Truth.assertThat(fakeCamera.cameraId).isEqualTo(fakeCameraId)
-        Truth.assertThat(fakeCamera.cameraDevice).isNotNull()
-        Truth.assertThat(fakeCamera.characteristics).isNotNull()
-        Truth.assertThat(fakeCamera.characteristics[CameraCharacteristics.LENS_FACING]).isNotNull()
-        Truth.assertThat(fakeCamera.metadata).isNotNull()
-        Truth.assertThat(fakeCamera.metadata[CameraCharacteristics.LENS_FACING]).isNotNull()
+        assertThat(fakeCamera).isNotNull()
+        assertThat(fakeCamera.cameraId).isEqualTo(fakeCameraId)
+        assertThat(fakeCamera.cameraDevice).isNotNull()
+        assertThat(fakeCamera.characteristics).isNotNull()
+        assertThat(fakeCamera.characteristics[CameraCharacteristics.LENS_FACING]).isNotNull()
+        assertThat(fakeCamera.metadata).isNotNull()
+        assertThat(fakeCamera.metadata[CameraCharacteristics.LENS_FACING]).isNotNull()
     }
 
     @After