Merge "Add a CameraGraph.Flag that disables graph level Surface tracking" into androidx-main
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 3ac0b9a..ff2a1a5 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -51,10 +51,13 @@
import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCameraDeviceOnCameraGraphCloseQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCaptureSessionOnDisconnectQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCaptureSessionOnVideoQuirk
+import androidx.camera.camera2.pipe.integration.compat.quirk.ConfigureSurfaceToSecondarySessionFailQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
import androidx.camera.camera2.pipe.integration.compat.quirk.DisableAbortCapturesOnStopWithSessionProcessorQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.FinalizeSessionOnCloseQuirk
+import androidx.camera.camera2.pipe.integration.compat.quirk.PreviewOrientationIncorrectQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.QuickSuccessiveImageCaptureFailsRepeatingRequestQuirk
+import androidx.camera.camera2.pipe.integration.compat.quirk.TextureViewIsClosedQuirk
import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride
import androidx.camera.camera2.pipe.integration.config.CameraConfig
import androidx.camera.camera2.pipe.integration.config.CameraScope
@@ -1117,6 +1120,13 @@
0u
}
+ val shouldDisableGraphLevelSurfaceTracking =
+ cameraQuirks.quirks.let {
+ it.contains(ConfigureSurfaceToSecondarySessionFailQuirk::class.java) ||
+ it.contains(PreviewOrientationIncorrectQuirk::class.java) ||
+ it.contains(TextureViewIsClosedQuirk::class.java)
+ }
+
return CameraGraph.Flags(
abortCapturesOnStop = shouldAbortCapturesOnStop,
awaitRepeatingRequestBeforeCapture =
@@ -1131,6 +1141,7 @@
closeCaptureSessionOnDisconnect = shouldCloseCaptureSessionOnDisconnect,
closeCameraDeviceOnClose = shouldCloseCameraDeviceOnClose,
finalizeSessionOnCloseBehavior = shouldFinalizeSessionOnCloseBehavior,
+ disableGraphLevelSurfaceTracking = shouldDisableGraphLevelSurfaceTracking,
)
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index 88d020d..fd51277 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -390,6 +390,16 @@
* - API levels: All
*/
val closeCameraDeviceOnClose: Boolean = false,
+
+ /**
+ * Flag to disable CameraGraph level Surface usage tracking. On legacy hardware levels, we
+ * need to explicitly relinquish current Surface usages on camera closure (or disconnection)
+ * such that CameraX can refresh the Surfaces used in the CameraGraph.
+ * - Bug(s): b/344749041
+ * - Device(s): LEGACY camera hardware level
+ * - API levels: 23 or LEGACY hardware level.
+ */
+ val disableGraphLevelSurfaceTracking: Boolean = false
) {
@JvmInline
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
index e7a6b9c..cdb0945 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
@@ -141,13 +141,15 @@
streamGraphImpl: StreamGraphImpl,
cameraController: CameraController,
cameraSurfaceManager: CameraSurfaceManager,
- imageSourceMap: ImageSourceMap
+ imageSourceMap: ImageSourceMap,
+ graphConfig: CameraGraph.Config,
): SurfaceGraph {
return SurfaceGraph(
streamGraphImpl,
cameraController,
cameraSurfaceManager,
- imageSourceMap.imageSources
+ imageSourceMap.imageSources,
+ graphConfig.flags,
)
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
index 76885a4..0992ece 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
@@ -35,7 +35,8 @@
private val streamGraphImpl: StreamGraphImpl,
private val cameraController: CameraController,
private val surfaceManager: CameraSurfaceManager,
- private val imageSources: Map<StreamId, ImageSource>
+ private val imageSources: Map<StreamId, ImageSource>,
+ private val cameraGraphFlags: CameraGraph.Flags,
) {
private val lock = Any()
@@ -66,8 +67,17 @@
"Removed surface for $streamId"
}
}
- var oldSurfaceToken: AutoCloseable? = null
+ if (cameraGraphFlags.disableGraphLevelSurfaceTracking) {
+ if (surface == null) {
+ surfaceMap.remove(streamId)
+ } else {
+ surfaceMap[streamId] = surface
+ }
+ return@synchronized null
+ }
+
+ var oldSurfaceToken: AutoCloseable? = null
if (surface == null) {
// TODO: Tell the graph processor that it should resubmit the repeating request
// or reconfigure the camera2 captureSession
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
index 709c0d1..c7e3f30 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
@@ -119,8 +119,15 @@
private val imageSourceMap = ImageSourceMap(graphConfig, streamGraph, imageSources)
private val frameDistributor =
FrameDistributor(imageSourceMap.imageSources, frameCaptureQueue) {}
+ private val cameraGraphFlags = CameraGraph.Flags()
private val surfaceGraph =
- SurfaceGraph(streamGraph, cameraController, cameraSurfaceManager, emptyMap())
+ SurfaceGraph(
+ streamGraph,
+ cameraController,
+ cameraSurfaceManager,
+ emptyMap(),
+ cameraGraphFlags,
+ )
private val audioRestriction = FakeAudioRestrictionController()
private val cameraGraph =
CameraGraphImpl(
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
index 4f7c6c4..a9a6c94 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
@@ -19,6 +19,7 @@
import android.graphics.SurfaceTexture
import android.os.Build
import android.view.Surface
+import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraSurfaceManager
import androidx.camera.camera2.pipe.testing.FakeCameraController
@@ -50,8 +51,15 @@
private val fakeSurfaceListener: CameraSurfaceManager.SurfaceListener = mock()
private val cameraSurfaceManager =
CameraSurfaceManager().also { it.addListener(fakeSurfaceListener) }
+ private val cameraGraphFlags = CameraGraph.Flags()
private val surfaceGraph =
- SurfaceGraph(streamMap, fakeCameraController, cameraSurfaceManager, emptyMap())
+ SurfaceGraph(
+ streamMap,
+ fakeCameraController,
+ cameraSurfaceManager,
+ emptyMap(),
+ cameraGraphFlags,
+ )
private val stream1 = streamMap[config.streamConfig1]!!
private val stream2 = streamMap[config.streamConfig2]!!
@@ -144,6 +152,45 @@
}
@Test
+ fun outputSurfacesArePassedToListenerWhenAvailableWithGraphTrackingOff() {
+ val surfaceGraph2 =
+ SurfaceGraph(
+ streamMap,
+ fakeCameraController,
+ cameraSurfaceManager,
+ emptyMap(),
+ cameraGraphFlags.copy(disableGraphLevelSurfaceTracking = true),
+ )
+
+ assertThat(fakeCameraController.surfaceMap).isNull()
+
+ surfaceGraph2[stream1.id] = fakeSurface1
+ surfaceGraph2[stream2.id] = fakeSurface2
+ surfaceGraph2[stream3.id] = fakeSurface3
+ surfaceGraph2[stream4.id] = fakeSurface4
+ surfaceGraph2[stream5.id] = fakeSurface5
+ surfaceGraph2[stream6.id] = fakeSurface6
+ surfaceGraph2[stream7.id] = fakeSurface7
+ surfaceGraph2[stream8.id] = fakeSurface8
+ assertThat(fakeCameraController.surfaceMap).isNull()
+
+ surfaceGraph2[stream9.id] = fakeSurface9
+ surfaceGraph2[stream10.id] = fakeSurface10
+
+ assertThat(fakeCameraController.surfaceMap).isNotNull()
+ assertThat(fakeCameraController.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface1)
+ assertThat(fakeCameraController.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface2)
+ assertThat(fakeCameraController.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface3)
+ assertThat(fakeCameraController.surfaceMap?.get(stream4.id)).isEqualTo(fakeSurface4)
+ assertThat(fakeCameraController.surfaceMap?.get(stream5.id)).isEqualTo(fakeSurface5)
+ assertThat(fakeCameraController.surfaceMap?.get(stream6.id)).isEqualTo(fakeSurface6)
+ assertThat(fakeCameraController.surfaceMap?.get(stream7.id)).isEqualTo(fakeSurface7)
+ assertThat(fakeCameraController.surfaceMap?.get(stream8.id)).isEqualTo(fakeSurface8)
+ assertThat(fakeCameraController.surfaceMap?.get(stream9.id)).isEqualTo(fakeSurface9)
+ assertThat(fakeCameraController.surfaceMap?.get(stream10.id)).isEqualTo(fakeSurface10)
+ }
+
+ @Test
fun onlyMostRecentSurfacesArePassedToSession() {
val fakeSurface1A = Surface(SurfaceTexture(7))
val fakeSurface1B = Surface(SurfaceTexture(8))
@@ -191,6 +238,26 @@
}
@Test
+ fun newSurfacesDoesNotAcquireTokensWithGraphTrackingOff() {
+ val surfaceGraph2 =
+ SurfaceGraph(
+ streamMap,
+ fakeCameraController,
+ cameraSurfaceManager,
+ emptyMap(),
+ cameraGraphFlags.copy(disableGraphLevelSurfaceTracking = true),
+ )
+ surfaceGraph2[stream1.id] = fakeSurface1
+
+ verify(fakeSurfaceListener, never()).onSurfaceActive(eq(fakeSurface1))
+ verify(fakeSurfaceListener, never()).onSurfaceActive(eq(fakeSurface2))
+ verify(fakeSurfaceListener, never()).onSurfaceActive(eq(fakeSurface3))
+ verify(fakeSurfaceListener, never()).onSurfaceInactive(eq(fakeSurface1))
+ verify(fakeSurfaceListener, never()).onSurfaceInactive(eq(fakeSurface2))
+ verify(fakeSurfaceListener, never()).onSurfaceInactive(eq(fakeSurface3))
+ }
+
+ @Test
fun replacingSurfacesReleasesPreviousToken() {
surfaceGraph[stream1.id] = fakeSurface1
surfaceGraph[stream1.id] = fakeSurface2
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt
index 34f4db5..f9ff936 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt
@@ -58,7 +58,6 @@
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.After
-import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
@@ -244,11 +243,6 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.M) // Known issue, checkout b/147393563.
fun canRecovered_afterReceivingCameraOnDisconnectedEvent() {
- // TODO(b/344749041) The tests can run failed on API 27 devices in camera-pipe config
- assumeFalse(
- Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1 &&
- implName == CameraPipeConfig::class.simpleName
- )
// Launch CameraX activity
cameraXActivityScenario = launchCameraXActivity(cameraId)
with(cameraXActivityScenario) {