[go: nahoru, domu]

In RequestWithCallback, no-op abort() call if the request is already complete

In a race condition, abort() might get called after failure/success. This change ensures that when that happens, the developer only gets one callback.

Bug: 225204995
Test: manual test and ./gradlew bOS
Change-Id: I7f68b375a4d25d3ede65583d2cd0848a281c6c8f
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
index c9816f5..dd26326 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
@@ -144,6 +144,10 @@
     @MainThread
     void abortAndSendErrorToApp(@NonNull ImageCaptureException imageCaptureException) {
         checkMainThread();
+        if (mCompleteFuture.isDone()) {
+            // The app has already received a callback. No need to abort.
+            return;
+        }
         abort();
         onFailure(imageCaptureException);
     }
@@ -151,6 +155,10 @@
     @MainThread
     void abortSilentlyAndRetry() {
         checkMainThread();
+        if (mCompleteFuture.isDone()) {
+            // The app has already received a callback. No need to abort.
+            return;
+        }
         abort();
         mRetryControl.retryRequest(mTakePictureRequest);
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/RequestWithCallbackTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/RequestWithCallbackTest.kt
index f0b794e..acdc78e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/RequestWithCallbackTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/RequestWithCallbackTest.kt
@@ -91,6 +91,37 @@
     }
 
     @Test
+    fun abortAndRetryAfterSuccess_abortIsNoOp() {
+        // Arrange.
+        val request = FakeTakePictureRequest(FakeTakePictureRequest.Type.IN_MEMORY)
+        val callback = RequestWithCallback(request, retryControl)
+
+        // Act: deliver result then abort
+        callback.onImageCaptured()
+        callback.onFinalResult(imageResult)
+        callback.abortSilentlyAndRetry()
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: retry is not queued.
+        assertThat(retryControl.retriedRequest).isNull()
+    }
+
+    @Test
+    fun abortAndFailAfterFail_abortIsNoOp() {
+        // Arrange.
+        val request = FakeTakePictureRequest(FakeTakePictureRequest.Type.IN_MEMORY)
+        val callback = RequestWithCallback(request, retryControl)
+
+        // Fail request then abort
+        callback.onCaptureFailure(otherError)
+        callback.abortAndSendErrorToApp(abortError)
+        shadowOf(getMainLooper()).idle()
+
+        // Assert:
+        assertThat(request.exceptionReceived).isEqualTo(otherError)
+    }
+
+    @Test
     fun abortRequestThenSendOtherErrors_receiveAbortError() {
         // Arrange.
         val request = FakeTakePictureRequest(FakeTakePictureRequest.Type.IN_MEMORY)