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(