Add signal to core test app to check when frames are streaming from different Preview use cases
- Use the ListenableFuture to signal the render that it completes to setup new surface for the Preview.
- Make sure FrameUpdateListener register to listen the frame streaming from the new Preview.
- remove TODO(b/159257773) and delay(500) in checkPreviewUpdatedAfterToggleCameraAndStopResume().
- remove TODO(b/159127941) in OpenGLRenderer. Xi had fixed it.
Bug: 159257773
Test: ./gradlew camera:integration-tests:camera-testapp-core:connectedCheck on different devices, Pixel 2, Nokia 1 and Samsung J2
Change-Id: I174a05ec4b10b78793a224ab6aee26d6872de85a
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
index 79ad758..2b2530f 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
@@ -33,7 +33,6 @@
import androidx.test.rule.GrantPermissionRule
import androidx.test.uiautomator.UiDevice
import androidx.testutils.withActivity
-import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.AfterClass
@@ -158,11 +157,6 @@
Espresso.onView(ViewMatchers.withId(R.id.direction_toggle))
.perform(ViewActions.click())
- // TODO(b/159257773): Currently have no reliable way of checking that camera has
- // switched. Delay to ensure previous camera has stopped streaming and the
- // idling resource actually is becoming idle due to frames from front camera.
- delay(500)
-
// Check front camera is now idle
withActivity { resetViewIdlingResource() }
waitForViewfinderIdle()
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index 0b6db82..6c2f515 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -81,6 +81,7 @@
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
import androidx.core.math.MathUtils;
+import androidx.core.util.Consumer;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.test.espresso.IdlingResource;
@@ -214,6 +215,20 @@
private CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener =
(compoundButton, isChecked) -> tryBindUseCases();
+ private Consumer<Long> mFrameUpdateListener = timestamp -> {
+ if (mPreviewFrameCount.getAndIncrement() >= FRAMES_UNTIL_VIEW_IS_READY) {
+ try {
+ if (!this.mViewIdlingResource.isIdleNow()) {
+ Log.d(TAG, FRAMES_UNTIL_VIEW_IS_READY + " or more counted on preview."
+ + " Make IdlingResource idle.");
+ this.mViewIdlingResource.decrement();
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Unexpected decrement. Continuing");
+ }
+ }
+ };
+
// Espresso testing variables
private final CountingIdlingResource mViewIdlingResource = new CountingIdlingResource("view");
private static final int FRAMES_UNTIL_VIEW_IS_READY = 5;
@@ -605,21 +620,6 @@
Objects.requireNonNull((DisplayManager) getSystemService(Context.DISPLAY_SERVICE));
dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
- previewRenderer.setFrameUpdateListener(ContextCompat.getMainExecutor(this), timestamp -> {
- // Wait until surface texture receives enough updates. This is for testing.
- if (mPreviewFrameCount.getAndIncrement() >= FRAMES_UNTIL_VIEW_IS_READY) {
- try {
- if (!mViewIdlingResource.isIdleNow()) {
- Log.d(TAG, FRAMES_UNTIL_VIEW_IS_READY + " or more counted on preview."
- + " Make IdlingResource idle.");
- mViewIdlingResource.decrement();
- }
- } catch (IllegalStateException e) {
- Log.e(TAG, "Unexpected decrement. Continuing");
- }
- }
- });
-
StrictMode.VmPolicy vmPolicy =
new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build();
StrictMode.setVmPolicy(vmPolicy);
@@ -691,6 +691,9 @@
// next thing being ready.
return;
}
+ // Clear listening frame update before unbind all.
+ mPreviewRenderer.clearFrameUpdateListener();
+
mCameraProvider.unbindAll();
try {
List<UseCase> useCases = buildUseCases();
@@ -725,7 +728,14 @@
.setTargetName("Preview")
.build();
resetViewIdlingResource();
- mPreviewRenderer.attachInputPreview(preview);
+ // Use the listener of the future to make sure the Preview setup the new surface.
+ mPreviewRenderer.attachInputPreview(preview).addListener(() -> {
+ Log.d(TAG, "OpenGLRenderer get the new surface for the Preview");
+ mPreviewRenderer.setFrameUpdateListener(
+ ContextCompat.getMainExecutor(this), mFrameUpdateListener
+ );
+ }, ContextCompat.getMainExecutor(this));
+
useCases.add(preview);
}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java
index 6e5feda..fe77d0b 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java
@@ -236,8 +236,9 @@
// with ConstraintLayout).
Preview preview = new Preview.Builder().setTargetAspectRatio(AspectRatio.RATIO_4_3).build();
- mRenderer.attachInputPreview(preview);
-
+ mRenderer.attachInputPreview(preview).addListener(() -> {
+ Log.d(TAG, "OpenGLRenderer get the new surface for the Preview");
+ }, ContextCompat.getMainExecutor(this));
CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
mCameraProvider.bindToLifecycle(this, cameraSelector, preview);
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
index 50646a9..6793c95 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
@@ -101,47 +101,63 @@
mExecutor.execute(() -> mNativeContext = initContext());
}
+ /**
+ * Attach the Preview to the renderer.
+ *
+ * @param preview Preview use-case used in the renderer.
+ * @return A {@link ListenableFuture} that signals the new surface is ready to be used in the
+ * renderer for the input Preview use-case.
+ */
@OptIn(markerClass = ExperimentalUseCaseGroup.class)
@MainThread
- void attachInputPreview(@NonNull Preview preview) {
- preview.setSurfaceProvider(
- mExecutor,
- surfaceRequest -> {
- if (mIsShutdown) {
- surfaceRequest.willNotProvideSurface();
- return;
- }
- SurfaceTexture surfaceTexture = resetPreviewTexture(
- surfaceRequest.getResolution());
- Surface inputSurface = new Surface(surfaceTexture);
- mNumOutstandingSurfaces++;
- surfaceRequest.setTransformationInfoListener(
- mExecutor,
- transformationInfo -> {
- mMvpDirty = true;
- // TODO(b/159127941): add the rotation to MVP transformation.
- if (!isCropRectFullTexture(transformationInfo.getCropRect())) {
- // Crop rect is pre-calculated. Use it directly.
- mPreviewCropRect = new RectF(
- transformationInfo.getCropRect());
- } else {
- // Crop rect needs to be calculated before drawing.
- mPreviewCropRect = null;
- }
- });
- surfaceRequest.provideSurface(
- inputSurface,
- mExecutor,
- result -> {
- inputSurface.release();
- surfaceTexture.release();
- if (surfaceTexture == mPreviewTexture) {
- mPreviewTexture = null;
- }
- mNumOutstandingSurfaces--;
- doShutdownExecutorIfNeeded();
- });
- });
+ @SuppressWarnings("ObjectToString")
+ @NonNull
+ ListenableFuture<Void> attachInputPreview(@NonNull Preview preview) {
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ preview.setSurfaceProvider(
+ mExecutor,
+ surfaceRequest -> {
+ if (mIsShutdown) {
+ surfaceRequest.willNotProvideSurface();
+ return;
+ }
+ SurfaceTexture surfaceTexture = resetPreviewTexture(
+ surfaceRequest.getResolution());
+ Surface inputSurface = new Surface(surfaceTexture);
+ mNumOutstandingSurfaces++;
+
+ surfaceRequest.setTransformationInfoListener(
+ mExecutor,
+ transformationInfo -> {
+ mMvpDirty = true;
+ if (!isCropRectFullTexture(transformationInfo.getCropRect())) {
+ // Crop rect is pre-calculated. Use it directly.
+ mPreviewCropRect = new RectF(
+ transformationInfo.getCropRect());
+ } else {
+ // Crop rect needs to be calculated before drawing.
+ mPreviewCropRect = null;
+ }
+ });
+
+ surfaceRequest.provideSurface(
+ inputSurface,
+ mExecutor,
+ result -> {
+ inputSurface.release();
+ surfaceTexture.release();
+ if (surfaceTexture == mPreviewTexture) {
+ mPreviewTexture = null;
+ }
+ mNumOutstandingSurfaces--;
+ doShutdownExecutorIfNeeded();
+ });
+ // Make sure the renderer use the new surface for the input Preview.
+ completer.set(null);
+
+ });
+ return "attachInputPreview [" + this + "]";
+ });
}
void attachOutputSurface(
@@ -187,6 +203,14 @@
}
}
+ void clearFrameUpdateListener() {
+ try {
+ mExecutor.execute(() -> mFrameUpdateListener = null);
+ } catch (RejectedExecutionException e) {
+ // Renderer is shutting down. Ignore.
+ }
+ }
+
void invalidateSurface(int surfaceRotationDegrees) {
try {
mExecutor.execute(