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)