[go: nahoru, domu]

Create helper class for creating PreviewSurfaceCallback

- Created a helper class for creating PreviewSurfaceCallback.
- Migrate all tests to use the new PreviewSurfaceCallback except for PreviewTest.
- Had to reorder some of the test because having both the new & the old interface is causing some funny side effect. It will be gone once the old interface is removed.

Bug:117519540
Test: manual test and ./gradlew bOS
Change-Id: I80d5c6f7549098a3106639f530ea5d5c6248e01b
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
index 8d6024a..1e11feb 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.camera2;
 
+import static androidx.camera.core.PreviewUtil.createPreviewSurfaceCallback;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -48,14 +50,13 @@
 import androidx.camera.core.Preview.OnPreviewOutputUpdateListener;
 import androidx.camera.core.Preview.PreviewOutput;
 import androidx.camera.core.PreviewConfig;
+import androidx.camera.core.PreviewUtil;
 import androidx.camera.core.SessionConfig;
-import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.testing.CameraUtil;
 import androidx.camera.testing.fakes.FakeCameraControl;
 import androidx.camera.testing.fakes.FakeLifecycleOwner;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.espresso.core.internal.deps.guava.base.Preconditions;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
@@ -63,8 +64,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.GrantPermissionRule;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -112,31 +111,19 @@
     private String mCameraId;
     private Semaphore mSurfaceFutureSemaphore;
     private Preview.PreviewSurfaceCallback mPreviewSurfaceCallbackWithFrameAvailableListener =
-            new Preview.PreviewSurfaceCallback() {
-
-                @NonNull
+            createPreviewSurfaceCallback(new PreviewUtil.SurfaceTextureCallback() {
                 @Override
-                public ListenableFuture<Surface> createSurfaceFuture(@NonNull Size resolution,
-                        int imageFormat) {
-                    Preconditions.checkNotNull(mSurfaceFutureSemaphore);
-                    SurfaceTexture surfaceTexture = new SurfaceTexture(0);
-                    surfaceTexture.setDefaultBufferSize(resolution.getWidth(),
-                            resolution.getHeight());
-                    surfaceTexture.detachFromGLContext();
+                public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture) {
                     surfaceTexture.setOnFrameAvailableListener(
                             surfaceTexture1 -> mSurfaceFutureSemaphore.release());
-                    return Futures.immediateFuture(new Surface(surfaceTexture));
                 }
 
                 @Override
-                public void onSafeToRelease(@NonNull ListenableFuture<Surface> surfaceFuture) {
-                    try {
-                        surfaceFuture.get().release();
-                    } catch (ExecutionException | InterruptedException e) {
-                        throw new IllegalStateException("Failed to release Surface");
-                    }
+                public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                    surfaceTexture.release();
                 }
-            };
+            });
+
 
     @Before
     public void setUp() {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
index 1c74981..f1bf29a 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.camera2;
 
+import static androidx.camera.core.PreviewUtil.createPreviewSurfaceCallback;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeTrue;
@@ -36,6 +38,7 @@
 import androidx.camera.core.ImageProxy;
 import androidx.camera.core.Preview;
 import androidx.camera.core.PreviewConfig;
+import androidx.camera.core.PreviewUtil;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.testing.CameraUtil;
 import androidx.camera.testing.GLUtil;
@@ -115,30 +118,26 @@
     public void previewCombinesImageCapture() throws InterruptedException {
         initPreview();
         initImageCapture();
+        mInstrumentation.runOnMainSync(() -> {
+            mPreview.setPreviewSurfaceCallback(createPreviewSurfaceCallback(
+                    new PreviewUtil.SurfaceTextureCallback() {
+                        @Override
+                        public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture) {
+                            surfaceTexture.attachToGLContext(GLUtil.getTexIdFromGLContext());
+                            surfaceTexture.setOnFrameAvailableListener(
+                                    surfaceTexture1 -> {
+                                        surfaceTexture.updateTexImage();
+                                        mSemaphore.release();
+                                    });
+                        }
 
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                CameraX.bindToLifecycle(mLifecycle, mPreview, mImageCapture);
-                mPreview.setOnPreviewOutputUpdateListener(
-                        new Preview.OnPreviewOutputUpdateListener() {
-                            @Override
-                            public void onUpdated(@NonNull Preview.PreviewOutput output) {
-                                output.getSurfaceTexture().attachToGLContext(
-                                        GLUtil.getTexIdFromGLContext());
-                                output.getSurfaceTexture().setOnFrameAvailableListener(
-                                        new SurfaceTexture.OnFrameAvailableListener() {
-                                            @Override
-                                            public void onFrameAvailable(
-                                                    SurfaceTexture surfaceTexture) {
-                                                surfaceTexture.updateTexImage();
-                                                mSemaphore.release();
-                                            }
-                                        });
-                            }
-                        });
-                mLifecycle.startAndResume();
-            }
+                        @Override
+                        public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                            surfaceTexture.release();
+                        }
+                    }));
+            CameraX.bindToLifecycle(mLifecycle, mPreview, mImageCapture);
+            mLifecycle.startAndResume();
         });
 
         // Wait for the frame available update.
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingSurfaceTextureTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingSurfaceTextureTest.java
index 87105b5..7b69793 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingSurfaceTextureTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingSurfaceTextureTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.core;
 
+import static androidx.camera.core.PreviewUtil.createPreviewSurfaceCallback;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.graphics.ImageFormat;
@@ -30,7 +32,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.testing.fakes.FakeCameraCaptureResult;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -44,7 +45,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -144,21 +144,25 @@
     public void writeToInputSurface_userOutputSurfaceReceivesFrame() throws ExecutionException,
             InterruptedException {
         // Arrange.
-        // Create a user provided Surface.
-        SurfaceTexture userSurfaceTexture = new SurfaceTexture(0);
-        userSurfaceTexture.setDefaultBufferSize(mResolution.getWidth(), mResolution.getHeight());
-        userSurfaceTexture.detachFromGLContext();
-        ListenableFuture<Surface> surfaceFuture =
-                Futures.immediateFuture(new Surface(userSurfaceTexture));
+        final Semaphore frameReceivedSemaphore = new Semaphore(0);
 
         // Create ProcessingSurfaceTexture with user Surface.
         ProcessingSurfaceTexture processingSurfaceTexture = createProcessingSurfaceTexture(
-                createCallbackDeferrableSurface(mResolution,
-                        CameraXExecutors.directExecutor(), surfaceFuture));
-        final Semaphore frameReceivedSemaphore = new Semaphore(0);
-        userSurfaceTexture.setOnFrameAvailableListener(
-                surfaceTexture -> frameReceivedSemaphore.release(),
-                mBackgroundHandler);
+                new CallbackDeferrableSurface(mResolution, CameraXExecutors.directExecutor(),
+                        createPreviewSurfaceCallback(new PreviewUtil.SurfaceTextureCallback() {
+                            @Override
+                            public void onSurfaceTextureReady(
+                                    @NonNull SurfaceTexture surfaceTexture) {
+                                surfaceTexture.setOnFrameAvailableListener(
+                                        surfaceTexture1 -> frameReceivedSemaphore.release(),
+                                        mBackgroundHandler);
+                            }
+
+                            @Override
+                            public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                                surfaceTexture.release();
+                            }
+                        })));
 
         // Act: Send one frame to processingSurfaceTexture.
         triggerImage(processingSurfaceTexture, 1);
@@ -183,28 +187,6 @@
                 callbackDeferrableSurface);
     }
 
-    private CallbackDeferrableSurface createCallbackDeferrableSurface(Size resolution,
-            Executor callbackExecutor, ListenableFuture<Surface> surfaceListenableFuture) {
-        return new CallbackDeferrableSurface(resolution, callbackExecutor,
-                new Preview.PreviewSurfaceCallback() {
-                    @NonNull
-                    @Override
-                    public ListenableFuture<Surface> createSurfaceFuture(@NonNull Size resolution,
-                            int imageFormat) {
-                        return surfaceListenableFuture;
-                    }
-
-                    @Override
-                    public void onSafeToRelease(@NonNull ListenableFuture<Surface> surfaceFuture) {
-                        try {
-                            surfaceFuture.get().release();
-                        } catch (ExecutionException | InterruptedException e) {
-                            // no-op
-                        }
-                    }
-                });
-    }
-
     @Test
     public void getSurfaceThrowsExceptionWhenClosed() {
         ProcessingSurfaceTexture processingSurfaceTexture = createProcessingSurfaceTexture();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/PreviewUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/PreviewUtil.java
new file mode 100644
index 0000000..aed998d
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/PreviewUtil.java
@@ -0,0 +1,110 @@
+/*
+ * 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.core;
+
+import android.graphics.SurfaceTexture;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.impl.utils.futures.Futures;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Helper class to be used with {@link Preview}.
+ */
+public final class PreviewUtil {
+
+    private static final String TAG = "PreviewUtil";
+
+    private PreviewUtil() {
+    }
+
+    /**
+     * Creates a {@link Preview.PreviewSurfaceCallback} that allocates and deallocates
+     * {@link SurfaceTexture}.
+     *
+     * @param surfaceTextureCallback listener that will be triggered when the SurfaceTexture is
+     *                                    ready.
+     * @return {@link Preview.PreviewSurfaceCallback} to be used with {@link Preview}.
+     */
+    @NonNull
+    public static Preview.PreviewSurfaceCallback createPreviewSurfaceCallback(
+            @NonNull SurfaceTextureCallback surfaceTextureCallback) {
+        return new Preview.PreviewSurfaceCallback() {
+
+            Map<Surface, SurfaceTexture> mSurfaceTextureMap = new HashMap<>();
+
+            @NonNull
+            @Override
+            public ListenableFuture<Surface> createSurfaceFuture(@NonNull Size resolution,
+                    int imageFormat) {
+                SurfaceTexture surfaceTexture = new SurfaceTexture(0);
+                surfaceTexture.setDefaultBufferSize(resolution.getWidth(),
+                        resolution.getHeight());
+                surfaceTexture.detachFromGLContext();
+                surfaceTextureCallback.onSurfaceTextureReady(surfaceTexture);
+                Surface surface = new Surface(surfaceTexture);
+                mSurfaceTextureMap.put(surface, surfaceTexture);
+                return Futures.immediateFuture(surface);
+            }
+
+            @Override
+            public void onSafeToRelease(@NonNull ListenableFuture<Surface> surfaceFuture) {
+                try {
+                    Surface surface = surfaceFuture.get();
+                    SurfaceTexture surfaceTexture = mSurfaceTextureMap.get(surface);
+                    if (surfaceTexture != null) {
+                        surfaceTextureCallback.onSafeToRelease(surfaceTexture);
+                    }
+                    surface.release();
+                } catch (ExecutionException | InterruptedException e) {
+                    Log.w(TAG, "Failed to release the Surface.", e);
+                }
+            }
+        };
+    }
+
+    /**
+     * Callback that is triggered when {@link SurfaceTexture} is ready.
+     */
+    public interface SurfaceTextureCallback {
+
+        /**
+         * Triggered when {@link SurfaceTexture} is ready.
+         *
+         * @param surfaceTexture {@link SurfaceTexture} created for {@link Preview}.
+         */
+        void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture);
+
+        /**
+         * Called when the {@link SurfaceTexture} is safe to release.
+         *
+         * <p> This method is called when the {@link SurfaceTexture} previously
+         * returned from {@link #onSurfaceTextureReady(SurfaceTexture)} is safe to be released.
+         *
+         * @param surfaceTexture the {@link SurfaceTexture} to release.
+         */
+        void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture);
+    }
+}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
index 8b8e187..4e172c3 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.extensions;
 
+import static androidx.camera.core.PreviewUtil.createPreviewSurfaceCallback;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.TestCase.assertEquals;
@@ -33,8 +35,10 @@
 import android.Manifest;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.graphics.SurfaceTexture;
 import android.os.Build;
 
+import androidx.annotation.NonNull;
 import androidx.camera.camera2.Camera2AppConfig;
 import androidx.camera.camera2.Camera2Config;
 import androidx.camera.camera2.impl.CameraEventCallback;
@@ -47,6 +51,7 @@
 import androidx.camera.core.ImageProxy;
 import androidx.camera.core.Preview;
 import androidx.camera.core.PreviewConfig;
+import androidx.camera.core.PreviewUtil;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.extensions.ExtensionsManager.EffectMode;
 import androidx.camera.extensions.util.ExtensionsTestUtil;
@@ -121,8 +126,6 @@
 
     @Test
     public void testCanBindToLifeCycleAndTakePicture() {
-        Preview.OnPreviewOutputUpdateListener mockOnPreviewOutputUpdateListener = mock(
-                Preview.OnPreviewOutputUpdateListener.class);
         ImageCapture.OnImageCapturedCallback mockOnImageCapturedCallback = mock(
                 ImageCapture.OnImageCapturedCallback.class);
 
@@ -133,10 +136,23 @@
 
         mInstrumentation.runOnMainSync(
                 () -> {
-                    CameraX.bindToLifecycle(mLifecycleOwner, preview, imageCapture);
-
                     // To set the update listener and Preview will change to active state.
-                    preview.setOnPreviewOutputUpdateListener(mockOnPreviewOutputUpdateListener);
+                    preview.setPreviewSurfaceCallback(createPreviewSurfaceCallback(
+                            new PreviewUtil.SurfaceTextureCallback() {
+                                @Override
+                                public void onSurfaceTextureReady(
+                                        @NonNull SurfaceTexture surfaceTexture) {
+                                    // No-op.
+                                }
+
+                                @Override
+                                public void onSafeToRelease(
+                                        @NonNull SurfaceTexture surfaceTexture) {
+                                    // No-op.
+                                }
+                            }));
+
+                    CameraX.bindToLifecycle(mLifecycleOwner, preview, imageCapture);
 
                     imageCapture.takePicture(CameraXExecutors.mainThreadExecutor(),
                             mockOnImageCapturedCallback);
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java
index d866652..b279950 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.extensions;
 
+import static androidx.camera.core.PreviewUtil.createPreviewSurfaceCallback;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeTrue;
@@ -32,6 +34,7 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
@@ -47,6 +50,7 @@
 import androidx.camera.core.CameraX.LensFacing;
 import androidx.camera.core.Preview;
 import androidx.camera.core.PreviewConfig;
+import androidx.camera.core.PreviewUtil;
 import androidx.camera.extensions.ExtensionsManager.EffectMode;
 import androidx.camera.extensions.impl.CaptureStageImpl;
 import androidx.camera.extensions.impl.PreviewExtenderImpl;
@@ -82,6 +86,20 @@
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private FakeLifecycleOwner mFakeLifecycle;
 
+    private static final PreviewUtil.SurfaceTextureCallback NO_OP_SURFACE_TEXTURE_CALLBACK =
+            new PreviewUtil.SurfaceTextureCallback() {
+                @Override
+                public void onSurfaceTextureReady(
+                        @NonNull SurfaceTexture surfaceTexture) {
+                    // No-op.
+                }
+
+                @Override
+                public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                    // No-op.
+                }
+            };
+
     @Rule
     public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
             Manifest.permission.CAMERA);
@@ -131,11 +149,11 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                CameraX.bindToLifecycle(mFakeLifecycle, useCase);
-
                 // To set the update listener and Preview will change to active state.
-                useCase.setOnPreviewOutputUpdateListener(
-                        mock(Preview.OnPreviewOutputUpdateListener.class));
+                useCase.setPreviewSurfaceCallback(
+                        createPreviewSurfaceCallback(NO_OP_SURFACE_TEXTURE_CALLBACK));
+
+                CameraX.bindToLifecycle(mFakeLifecycle, useCase);
             }
         });
 
@@ -208,11 +226,11 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                CameraX.bindToLifecycle(mFakeLifecycle, preview);
-
                 // To set the update listener and Preview will change to active state.
-                preview.setOnPreviewOutputUpdateListener(
-                        mock(Preview.OnPreviewOutputUpdateListener.class));
+                preview.setPreviewSurfaceCallback(
+                        createPreviewSurfaceCallback(NO_OP_SURFACE_TEXTURE_CALLBACK));
+
+                CameraX.bindToLifecycle(mFakeLifecycle, preview);
             }
         });
 
@@ -259,10 +277,11 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                CameraX.bindToLifecycle(mFakeLifecycle, preview);
                 // To set the update listener and Preview will change to active state.
-                preview.setOnPreviewOutputUpdateListener(
-                        mock(Preview.OnPreviewOutputUpdateListener.class));
+                preview.setPreviewSurfaceCallback(
+                        createPreviewSurfaceCallback(NO_OP_SURFACE_TEXTURE_CALLBACK));
+
+                CameraX.bindToLifecycle(mFakeLifecycle, preview);
             }
         });
 
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/activity/CameraXTestActivity.java b/camera/camera-testing/src/main/java/androidx/camera/testing/activity/CameraXTestActivity.java
index af38bb6..e17677a 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/activity/CameraXTestActivity.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/activity/CameraXTestActivity.java
@@ -16,6 +16,9 @@
 
 package androidx.camera.testing.activity;
 
+
+import static androidx.camera.core.PreviewUtil.createPreviewSurfaceCallback;
+
 import android.graphics.SurfaceTexture;
 import android.os.Bundle;
 import android.util.Log;
@@ -30,6 +33,7 @@
 import androidx.camera.core.CameraX;
 import androidx.camera.core.Preview;
 import androidx.camera.core.PreviewConfig;
+import androidx.camera.core.PreviewUtil;
 import androidx.camera.testing.CameraUtil;
 import androidx.camera.testing.R;
 import androidx.test.espresso.idling.CountingIdlingResource;
@@ -89,18 +93,21 @@
 
         mPreview = new Preview(config);
         TextureView textureView = findViewById(R.id.textureView);
-        mPreview.setOnPreviewOutputUpdateListener(
-                new Preview.OnPreviewOutputUpdateListener() {
+        mPreview.setPreviewSurfaceCallback(createPreviewSurfaceCallback(
+                new PreviewUtil.SurfaceTextureCallback() {
                     @Override
-                    public void onUpdated(Preview.PreviewOutput previewOutput) {
-                        // If TextureView was already created, need to re-add it to change the
-                        // SurfaceTexture.
+                    public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture) {
                         ViewGroup viewGroup = (ViewGroup) textureView.getParent();
                         viewGroup.removeView(textureView);
                         viewGroup.addView(textureView);
-                        textureView.setSurfaceTexture(previewOutput.getSurfaceTexture());
+                        textureView.setSurfaceTexture(surfaceTexture);
                     }
-                });
+
+                    @Override
+                    public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                        surfaceTexture.release();
+                    }
+                }));
 
         try {
             CameraX.bindToLifecycle(this, mPreview);
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewMeteringPointFactoryTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewMeteringPointFactoryTest.java
index b023a6c..7a0140a 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewMeteringPointFactoryTest.java
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewMeteringPointFactoryTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.view;
 
+import static androidx.camera.core.PreviewUtil.createPreviewSurfaceCallback;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeTrue;
@@ -38,6 +40,7 @@
 import androidx.camera.core.MeteringPointFactory;
 import androidx.camera.core.Preview;
 import androidx.camera.core.PreviewConfig;
+import androidx.camera.core.PreviewUtil;
 import androidx.camera.testing.CameraUtil;
 import androidx.camera.testing.CoreAppTestUtil;
 import androidx.camera.testing.fakes.FakeActivity;
@@ -196,43 +199,41 @@
                         .setLensFacing(lensFacing);
 
         Preview preview = new Preview(previewConfigBuilder.build());
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                preview.setOnPreviewOutputUpdateListener(
-                        new Preview.OnPreviewOutputUpdateListener() {
-                            @Override
-                            public void onUpdated(@NonNull Preview.PreviewOutput output) {
-                                mActivityRule.getActivity().runOnUiThread(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        ViewGroup viewGroup = (ViewGroup) mTextureView.getParent();
-                                        viewGroup.removeView(mTextureView);
-                                        viewGroup.addView(mTextureView);
+        mInstrumentation.runOnMainSync(() -> {
+            preview.setPreviewSurfaceCallback(createPreviewSurfaceCallback(
+                    new PreviewUtil.SurfaceTextureCallback() {
+                        @Override
+                        public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture) {
+                            ViewGroup viewGroup = (ViewGroup) mTextureView.getParent();
+                            viewGroup.removeView(mTextureView);
+                            viewGroup.addView(mTextureView);
+                            mTextureView.setSurfaceTexture(surfaceTexture);
 
-                                        mTextureView.setSurfaceTexture(output.getSurfaceTexture());
-                                        output.getSurfaceTexture().setOnFrameAvailableListener(
-                                                new SurfaceTexture.OnFrameAvailableListener() {
-                                                    int mFrameCount = 0;
-                                                    @Override
-                                                    public void onFrameAvailable(
-                                                            SurfaceTexture surfaceTexture) {
-                                                        mFrameCount++;
-                                                        if (mFrameCount == WAIT_FRAMECOUNT) {
-                                                            mLatchForFrameReady.countDown();
-                                                        }
-                                                    }
-                                                });
-                                    }
-                                });
-                            }
-                        });
+                            surfaceTexture.setOnFrameAvailableListener(
+                                    new SurfaceTexture.OnFrameAvailableListener() {
+                                        int mFrameCount = 0;
 
-                // SurfaceTexture#getTransformMatrix is initialized properly when camera starts
-                // to output.
-                CameraX.bindToLifecycle(mLifecycle, preview);
-                mLifecycle.startAndResume();
-            }
+                                        @Override
+                                        public void onFrameAvailable(
+                                                SurfaceTexture surfaceTexture) {
+                                            mFrameCount++;
+                                            if (mFrameCount == WAIT_FRAMECOUNT) {
+                                                mLatchForFrameReady.countDown();
+                                            }
+                                        }
+                                    });
+                        }
+
+                        @Override
+                        public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                            surfaceTexture.release();
+                        }
+                    }));
+
+            // SurfaceTexture#getTransformMatrix is initialized properly when camera starts
+            // to output.
+            CameraX.bindToLifecycle(mLifecycle, preview);
+            mLifecycle.startAndResume();
         });
 
         mLatchForFrameReady.await(3, TimeUnit.SECONDS);