Merge "Create a FakeSurfaceEffect for testing." into androidx-main am: 1943f12168

Original change: https://android-review.googlesource.com/c/platform/frameworks/support/+/2159637

Change-Id: Ia30587010119c86f6d7c0ecef5155cba2ba29867
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
index 85572f1..616fb90 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
@@ -19,6 +19,7 @@
 import android.os.Build
 import androidx.camera.core.SurfaceEffect.PREVIEW
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
+import androidx.camera.testing.fakes.FakeSurfaceEffect
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,11 +42,7 @@
 
     @Test(expected = IllegalArgumentException::class)
     fun addMoreThanOnePreviewEffect_throwsException() {
-        val surfaceEffect = object : SurfaceEffect {
-            override fun onInputSurface(request: SurfaceRequest) {}
-
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {}
-        }
+        val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
         EffectBundle.Builder(mainThreadExecutor())
             .addEffect(PREVIEW, surfaceEffect)
             .addEffect(PREVIEW, surfaceEffect)
@@ -54,11 +51,7 @@
     @Test
     fun addPreviewEffect_hasPreviewEffect() {
         // Arrange.
-        val surfaceEffect = object : SurfaceEffect {
-            override fun onInputSurface(request: SurfaceRequest) {}
-
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {}
-        }
+        val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
         // Act.
         val effectBundle = EffectBundle.Builder(mainThreadExecutor())
             .addEffect(PREVIEW, surfaceEffect)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 1be7cf1..0cae6de 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -42,6 +42,7 @@
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.fakes.FakeCameraFactory
+import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
@@ -71,8 +72,6 @@
 
     private lateinit var appSurface: Surface
     private lateinit var appSurfaceTexture: SurfaceTexture
-    private lateinit var effectSurface: Surface
-    private lateinit var effectSurfaceTexture: SurfaceTexture
     private lateinit var camera: FakeCamera
     private lateinit var cameraXConfig: CameraXConfig
     private lateinit var context: Context
@@ -82,8 +81,6 @@
     fun setUp() {
         appSurfaceTexture = SurfaceTexture(0)
         appSurface = Surface(appSurfaceTexture)
-        effectSurfaceTexture = SurfaceTexture(0)
-        effectSurface = Surface(effectSurfaceTexture)
         camera = FakeCamera()
 
         val cameraFactoryProvider =
@@ -106,8 +103,6 @@
     fun tearDown() {
         appSurfaceTexture.release()
         appSurface.release()
-        effectSurfaceTexture.release()
-        effectSurface.release()
         with(cameraUseCaseAdapter) {
             this?.removeUseCases(useCases)
         }
@@ -222,27 +217,10 @@
     @Test
     fun bindAndUnbindPreview_surfacesPropagated() {
         // Arrange.
-        var surfaceOutputReceived: SurfaceOutput? = null
-        var effectSurfaceReadyToRelease = false
-        var isEffectReleased = false
-        val surfaceEffect = object : SurfaceEffectInternal {
-            override fun onInputSurface(request: SurfaceRequest) {
-                request.provideSurface(effectSurface, mainThreadExecutor()) {
-                    effectSurfaceReadyToRelease = true
-                }
-            }
-
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
-                surfaceOutputReceived = surfaceOutput
-            }
-
-            override fun release() {
-                isEffectReleased = true
-            }
-        }
+        val effect = FakeSurfaceEffectInternal(mainThreadExecutor())
 
         // Act: create pipeline in Preview and provide Surface.
-        val preview = createPreviewPipelineAndAttachEffect(surfaceEffect)
+        val preview = createPreviewPipelineAndAttachEffect(effect)
         val surfaceRequest = preview.mCurrentSurfaceRequest!!
         var appSurfaceReadyToRelease = false
         surfaceRequest.provideSurface(appSurface, mainThreadExecutor()) {
@@ -251,30 +229,26 @@
         shadowOf(getMainLooper()).idle()
 
         // Assert: surfaceOutput received.
-        assertThat(surfaceOutputReceived).isNotNull()
-        var requestedToReleaseOutputSurface = false
-        surfaceOutputReceived!!.getSurface(mainThreadExecutor()) {
-            requestedToReleaseOutputSurface = true
-        }
-        assertThat(isEffectReleased).isFalse()
-        assertThat(requestedToReleaseOutputSurface).isFalse()
-        assertThat(effectSurfaceReadyToRelease).isFalse()
+        assertThat(effect.surfaceOutput).isNotNull()
+        assertThat(effect.isReleased).isFalse()
+        assertThat(effect.isOutputSurfaceRequestedToClose).isFalse()
+        assertThat(effect.isInputSurfaceReleased).isFalse()
         assertThat(appSurfaceReadyToRelease).isFalse()
         // effect surface is provided to camera.
-        assertThat(preview.sessionConfig.surfaces[0].surface.get()).isEqualTo(effectSurface)
+        assertThat(preview.sessionConfig.surfaces[0].surface.get()).isEqualTo(effect.inputSurface)
 
         // Act: unbind Preview.
         preview.onDetached()
         shadowOf(getMainLooper()).idle()
 
         // Assert: effect and effect surface is released.
-        assertThat(isEffectReleased).isTrue()
-        assertThat(requestedToReleaseOutputSurface).isTrue()
-        assertThat(effectSurfaceReadyToRelease).isTrue()
+        assertThat(effect.isReleased).isTrue()
+        assertThat(effect.isOutputSurfaceRequestedToClose).isTrue()
+        assertThat(effect.isInputSurfaceReleased).isTrue()
         assertThat(appSurfaceReadyToRelease).isFalse()
 
         // Act: close SurfaceOutput
-        surfaceOutputReceived!!.close()
+        effect.surfaceOutput!!.close()
         shadowOf(getMainLooper()).idle()
         assertThat(appSurfaceReadyToRelease).isTrue()
     }
@@ -282,18 +256,8 @@
     @Test
     fun invokedErrorListener_recreatePipeline() {
         // Arrange: create pipeline and get a reference of the SessionConfig.
-        val surfaceEffect = object : SurfaceEffectInternal {
-            override fun onInputSurface(request: SurfaceRequest) {}
-
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
-                surfaceOutput.getSurface(mainThreadExecutor()) {
-                    surfaceOutput.close()
-                }
-            }
-
-            override fun release() {}
-        }
-        val preview = createPreviewPipelineAndAttachEffect(surfaceEffect)
+        val effect = FakeSurfaceEffectInternal(mainThreadExecutor())
+        val preview = createPreviewPipelineAndAttachEffect(effect)
         val originalSessionConfig = preview.sessionConfig
 
         // Act: invoke the error listener.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index 42f71acb..951f021 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -19,11 +19,10 @@
 import android.os.Build
 import androidx.camera.core.EffectBundle
 import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceEffect.PREVIEW
-import androidx.camera.core.SurfaceOutput
-import androidx.camera.core.SurfaceRequest
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.processing.SurfaceEffectWithExecutor
+import androidx.camera.testing.fakes.FakeSurfaceEffect
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
@@ -43,22 +42,20 @@
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class CameraUseCaseAdapterTest {
 
-    private lateinit var surfaceEffect: SurfaceEffect
+    private lateinit var surfaceEffect: FakeSurfaceEffect
     private lateinit var mEffectBundle: EffectBundle
     private lateinit var executor: ExecutorService
 
     @Before
     fun setUp() {
-        surfaceEffect = object : SurfaceEffect {
-            override fun onInputSurface(request: SurfaceRequest) {}
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {}
-        }
+        surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
         executor = Executors.newSingleThreadExecutor()
         mEffectBundle = EffectBundle.Builder(executor).addEffect(PREVIEW, surfaceEffect).build()
     }
 
     @After
     fun tearDown() {
+        surfaceEffect.cleanUp()
         executor.shutdown()
     }
 
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
index ca79bdea..7df85dd 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
@@ -24,11 +24,10 @@
 import android.util.Size
 import android.view.Surface
 import androidx.camera.core.SurfaceEffect.PREVIEW
-import androidx.camera.core.SurfaceOutput
-import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -55,15 +54,9 @@
         private val CROP_RECT = Rect(0, 0, 600, 400)
     }
 
-    private lateinit var surfaceEffect: SurfaceEffectInternal
-    private var isReleased = false
-    private var surfaceOutputCloseRequested = false
-    private var surfaceOutputReceived: SurfaceOutput? = null
-    private var surfaceReceivedByEffect: Surface? = null
+    private lateinit var surfaceEffectInternal: FakeSurfaceEffectInternal
     private lateinit var appSurface: Surface
     private lateinit var appSurfaceTexture: SurfaceTexture
-    private lateinit var effectSurface: Surface
-    private lateinit var effectSurfaceTexture: SurfaceTexture
     private lateinit var node: SurfaceEffectNode
     private lateinit var inputEdge: SurfaceEdge
 
@@ -71,30 +64,8 @@
     fun setup() {
         appSurfaceTexture = SurfaceTexture(0)
         appSurface = Surface(appSurfaceTexture)
-        effectSurfaceTexture = SurfaceTexture(0)
-        effectSurface = Surface(effectSurfaceTexture)
-
-        surfaceEffect = object : SurfaceEffectInternal {
-            override fun onInputSurface(request: SurfaceRequest) {
-                request.provideSurface(effectSurface, mainThreadExecutor()) {
-                    effectSurfaceTexture.release()
-                    effectSurface.release()
-                }
-            }
-
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
-                surfaceOutputReceived = surfaceOutput
-                surfaceReceivedByEffect = surfaceOutput.getSurface(mainThreadExecutor()) {
-                    surfaceOutput.close()
-                    surfaceOutputCloseRequested = true
-                }
-            }
-
-            override fun release() {
-                isReleased = true
-            }
-        }
-        node = SurfaceEffectNode(FakeCamera(), surfaceEffect)
+        surfaceEffectInternal = FakeSurfaceEffectInternal(mainThreadExecutor())
+        node = SurfaceEffectNode(FakeCamera(), surfaceEffectInternal)
         inputEdge = createInputEdge()
     }
 
@@ -102,8 +73,7 @@
     fun tearDown() {
         appSurfaceTexture.release()
         appSurface.release()
-        effectSurfaceTexture.release()
-        effectSurface.release()
+        surfaceEffectInternal.release()
         node.release()
         inputEdge.surfaces[0].close()
         shadowOf(getMainLooper()).idle()
@@ -140,8 +110,8 @@
         shadowOf(getMainLooper()).idle()
 
         // Assert: effect receives app Surface. CameraX receives effect Surface.
-        assertThat(surfaceReceivedByEffect).isEqualTo(appSurface)
-        assertThat(inputSurface.surface.get()).isEqualTo(effectSurface)
+        assertThat(surfaceEffectInternal.outputSurface).isEqualTo(appSurface)
+        assertThat(inputSurface.surface.get()).isEqualTo(surfaceEffectInternal.inputSurface)
     }
 
     @Test
@@ -156,8 +126,8 @@
         shadowOf(getMainLooper()).idle()
 
         // Assert: effect is released and has requested effect to close the SurfaceOutput
-        assertThat(isReleased).isTrue()
-        assertThat(surfaceOutputCloseRequested).isTrue()
+        assertThat(surfaceEffectInternal.isReleased).isTrue()
+        assertThat(surfaceEffectInternal.isOutputSurfaceRequestedToClose).isTrue()
     }
 
     private fun createInputEdge(): SurfaceEdge {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
index 86b492e..6a33afe 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
@@ -27,6 +27,7 @@
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
 import com.google.common.truth.Truth.assertThat
 import java.lang.Thread.currentThread
 import java.util.concurrent.Executor
@@ -69,13 +70,10 @@
 
     @Test(expected = IllegalStateException::class)
     fun initWithSurfaceEffectInternal_throwsException() {
-        SurfaceEffectWithExecutor(object : SurfaceEffectInternal {
-            override fun onInputSurface(request: SurfaceRequest) {}
-
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {}
-
-            override fun release() {}
-        }, mainThreadExecutor())
+        SurfaceEffectWithExecutor(
+            FakeSurfaceEffectInternal(mainThreadExecutor()),
+            mainThreadExecutor()
+        )
     }
 
     @Test
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index bf87a7f..35e8e77 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -26,10 +26,7 @@
 import androidx.camera.core.CameraXConfig
 import androidx.camera.core.EffectBundle
 import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceEffect.PREVIEW
-import androidx.camera.core.SurfaceOutput
-import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.UseCaseGroup
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -40,6 +37,7 @@
 import androidx.camera.testing.fakes.FakeCameraFactory
 import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import androidx.camera.testing.fakes.FakeLifecycleOwner
+import androidx.camera.testing.fakes.FakeSurfaceEffect
 import androidx.camera.testing.fakes.FakeUseCaseConfigFactory
 import androidx.concurrent.futures.await
 import androidx.test.core.app.ApplicationProvider
@@ -81,12 +79,7 @@
     fun bindUseCaseGroupWithEffect_effectIsSetOnUseCase() {
         // Arrange.
         ProcessCameraProvider.configureInstance(FakeAppConfig.create())
-        val surfaceEffect = object : SurfaceEffect {
-            override fun onInputSurface(request: SurfaceRequest) {}
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
-                surfaceOutput.close()
-            }
-        }
+        val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
         val effectBundle =
             EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceEffect).build()
         val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java
new file mode 100644
index 0000000..1287dee
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 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.testing.fakes;
+
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceRequest;
+import androidx.camera.core.impl.DeferrableSurface;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Fake {@link SurfaceEffect} used in tests.
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class FakeSurfaceEffect implements SurfaceEffect {
+
+    final SurfaceTexture mSurfaceTexture;
+    final Surface mInputSurface;
+    private final Executor mExecutor;
+
+
+    @Nullable
+    private SurfaceRequest mSurfaceRequest;
+    @Nullable
+    private SurfaceOutput mSurfaceOutput;
+    boolean mIsInputSurfaceReleased;
+    boolean mIsOutputSurfaceRequestedToClose;
+
+    Surface mOutputSurface;
+
+    public FakeSurfaceEffect(@NonNull Executor executor) {
+        mSurfaceTexture = new SurfaceTexture(0);
+        mInputSurface = new Surface(mSurfaceTexture);
+        mExecutor = executor;
+        mIsInputSurfaceReleased = false;
+        mIsOutputSurfaceRequestedToClose = false;
+    }
+
+    @Override
+    public void onInputSurface(@NonNull SurfaceRequest request) {
+        mSurfaceRequest = request;
+        request.provideSurface(mInputSurface, mExecutor, result -> {
+            mSurfaceTexture.release();
+            mInputSurface.release();
+            mIsInputSurfaceReleased = true;
+        });
+    }
+
+    @Override
+    public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) {
+        mSurfaceOutput = surfaceOutput;
+        mOutputSurface = surfaceOutput.getSurface(mExecutor,
+                () -> mIsOutputSurfaceRequestedToClose = true);
+    }
+
+    @Nullable
+    public SurfaceRequest getSurfaceRequest() {
+        return mSurfaceRequest;
+    }
+
+    @Nullable
+    public SurfaceOutput getSurfaceOutput() {
+        return mSurfaceOutput;
+    }
+
+    @NonNull
+    public Surface getInputSurface() {
+        return mInputSurface;
+    }
+
+    @NonNull
+    public Surface getOutputSurface() {
+        return mOutputSurface;
+    }
+
+    public boolean isInputSurfaceReleased() {
+        return mIsInputSurfaceReleased;
+    }
+
+    public boolean isOutputSurfaceRequestedToClose() {
+        return mIsOutputSurfaceRequestedToClose;
+    }
+
+    /**
+     * Clear up the instance to avoid the "{@link DeferrableSurface} garbage collected" error.
+     */
+    public void cleanUp() {
+        if (mSurfaceRequest != null) {
+            mSurfaceRequest.willNotProvideSurface();
+        }
+        if (mSurfaceOutput != null) {
+            mSurfaceOutput.close();
+        }
+        mSurfaceTexture.release();
+        mInputSurface.release();
+    }
+
+    @Override
+    protected void finalize() {
+        cleanUp();
+    }
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java
new file mode 100644
index 0000000..47bd6f7
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 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.testing.fakes;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.processing.SurfaceEffectInternal;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Fake {@link SurfaceEffectInternal} used in tests.
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class FakeSurfaceEffectInternal extends FakeSurfaceEffect implements SurfaceEffectInternal {
+
+    private boolean mIsReleased;
+
+    public FakeSurfaceEffectInternal(@NonNull Executor executor) {
+        super(executor);
+        mIsReleased = false;
+    }
+
+    public boolean isReleased() {
+        return mIsReleased;
+    }
+
+    @Override
+    public void release() {
+        mIsReleased = true;
+    }
+}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
index c478811..2e5c7b8 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
@@ -25,10 +25,7 @@
 import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
 import androidx.camera.core.EffectBundle
 import androidx.camera.core.ImageCapture
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceEffect.PREVIEW
-import androidx.camera.core.SurfaceOutput
-import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.CameraUtil
@@ -36,6 +33,7 @@
 import androidx.camera.testing.CoreAppTestUtil
 import androidx.camera.testing.fakes.FakeActivity
 import androidx.camera.testing.fakes.FakeLifecycleOwner
+import androidx.camera.testing.fakes.FakeSurfaceEffect
 import androidx.test.annotation.UiThreadTest
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
@@ -105,15 +103,10 @@
 
         // Act: set an EffectBundle
         instrumentation.runOnMainSync {
-            val surfaceEffect = object : SurfaceEffect {
-                override fun onInputSurface(request: SurfaceRequest) {}
-
-                override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
-                    surfaceOutput.close()
-                }
-            }
             controller.setEffectBundle(
-                EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceEffect).build()
+                EffectBundle.Builder(mainThreadExecutor())
+                    .addEffect(PREVIEW, FakeSurfaceEffect(mainThreadExecutor()))
+                    .build()
             )
         }