diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 8525ad1e21d8..5fd62d2b716b 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.0+1 + +* Fixes picture captures causing a crash on some Huawei devices. + ## 0.7.0 * Added support for capture orientation locking on Android and iOS. diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 10d58f5e8792..d5a5c5cfeb6b 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -458,17 +458,20 @@ public void onCaptureFailed( return; } String reason; + boolean fatalFailure = false; switch (failure.getReason()) { case CaptureFailure.REASON_ERROR: reason = "An error happened in the framework"; break; case CaptureFailure.REASON_FLUSHED: reason = "The capture has failed due to an abortCaptures() call"; + fatalFailure = true; break; default: reason = "Unknown reason"; } - pictureCaptureRequest.error("captureFailure", reason, null); + Log.w("Camera", "pictureCaptureCallback.onCaptureFailed(): " + reason); + if (fatalFailure) pictureCaptureRequest.error("captureFailure", reason, null); } private void processCapture(CaptureResult result) { diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java index 189f2f1490dc..396f782a2a08 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -4,6 +4,8 @@ package io.flutter.plugins.camera; +import android.os.Handler; +import android.os.Looper; import androidx.annotation.Nullable; import io.flutter.plugin.common.MethodChannel; @@ -19,17 +21,36 @@ enum State { error, } + private final Runnable timeoutCallback = + new Runnable() { + @Override + public void run() { + error("captureTimeout", "Picture capture request timed out", state.toString()); + } + }; + private final MethodChannel.Result result; + private final TimeoutHandler timeoutHandler; private State state; public PictureCaptureRequest(MethodChannel.Result result) { + this(result, new TimeoutHandler()); + } + + public PictureCaptureRequest(MethodChannel.Result result, TimeoutHandler timeoutHandler) { this.result = result; - state = State.idle; + this.state = State.idle; + this.timeoutHandler = timeoutHandler; } public void setState(State state) { if (isFinished()) throw new IllegalStateException("Request has already been finished"); this.state = state; + if (state != State.idle && state != State.finished && state != State.error) { + this.timeoutHandler.resetTimeout(timeoutCallback); + } else { + this.timeoutHandler.clearTimeout(timeoutCallback); + } } public State getState() { @@ -42,6 +63,7 @@ public boolean isFinished() { public void finish(String absolutePath) { if (isFinished()) throw new IllegalStateException("Request has already been finished"); + this.timeoutHandler.clearTimeout(timeoutCallback); result.success(absolutePath); state = State.finished; } @@ -49,7 +71,26 @@ public void finish(String absolutePath) { public void error( String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { if (isFinished()) throw new IllegalStateException("Request has already been finished"); + this.timeoutHandler.clearTimeout(timeoutCallback); result.error(errorCode, errorMessage, errorDetails); state = State.error; } + + static class TimeoutHandler { + private static final int REQUEST_TIMEOUT = 5000; + private final Handler handler; + + TimeoutHandler() { + this.handler = new Handler(Looper.getMainLooper()); + } + + public void resetTimeout(Runnable runnable) { + clearTimeout(runnable); + handler.postDelayed(runnable, REQUEST_TIMEOUT); + } + + public void clearTimeout(Runnable runnable) { + handler.removeCallbacks(runnable); + } + } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java index 3ede0b7abe3a..3252b3e111c4 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -7,7 +7,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import io.flutter.plugin.common.MethodChannel; @@ -38,6 +41,32 @@ public void setState_sets_state() { "State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing); } + @Test + public void setState_resets_timeout() { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler); + req.setState(PictureCaptureRequest.State.focusing); + req.setState(PictureCaptureRequest.State.preCapture); + req.setState(PictureCaptureRequest.State.waitingPreCaptureReady); + req.setState(PictureCaptureRequest.State.capturing); + verify(mockTimeoutHandler, times(4)).resetTimeout(any()); + verify(mockTimeoutHandler, never()).clearTimeout(any()); + } + + @Test + public void setState_clears_timeout() { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler); + req.setState(PictureCaptureRequest.State.idle); + req.setState(PictureCaptureRequest.State.finished); + req = new PictureCaptureRequest(null, mockTimeoutHandler); + req.setState(PictureCaptureRequest.State.error); + verify(mockTimeoutHandler, never()).resetTimeout(any()); + verify(mockTimeoutHandler, times(3)).clearTimeout(any()); + } + @Test public void finish_sets_result_and_state() { // Setup @@ -50,6 +79,17 @@ public void finish_sets_result_and_state() { assertEquals("State is finished", req.getState(), PictureCaptureRequest.State.finished); } + @Test + public void finish_clears_timeout() { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler); + req.finish("/test/path"); + verify(mockTimeoutHandler, never()).resetTimeout(any()); + verify(mockTimeoutHandler).clearTimeout(any()); + } + @Test public void isFinished_is_true_When_state_is_finished_or_error() { // Setup @@ -90,6 +130,17 @@ public void error_sets_result_and_state() { assertEquals("State is error", req.getState(), PictureCaptureRequest.State.error); } + @Test + public void error_clears_timeout() { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler); + req.error("ERROR_CODE", "Error Message", null); + verify(mockTimeoutHandler, never()).resetTimeout(any()); + verify(mockTimeoutHandler).clearTimeout(any()); + } + @Test(expected = IllegalStateException.class) public void error_throws_When_already_finished() { // Setup diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index b0ebb9c16361..b406ce5ba64f 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.7.0 +version: 0.7.0+1 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: