Skip to content

Commit 6d18db8

Browse files
authored
[camera] Fix picture capture causing a crash on some Huawei devices. (flutter#3414)
1 parent edda73d commit 6d18db8

File tree

5 files changed

+102
-3
lines changed

5 files changed

+102
-3
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.7.0+1
2+
3+
* Fixes picture captures causing a crash on some Huawei devices.
4+
15
## 0.7.0
26

37
* Added support for capture orientation locking on Android and iOS.

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,17 +458,20 @@ public void onCaptureFailed(
458458
return;
459459
}
460460
String reason;
461+
boolean fatalFailure = false;
461462
switch (failure.getReason()) {
462463
case CaptureFailure.REASON_ERROR:
463464
reason = "An error happened in the framework";
464465
break;
465466
case CaptureFailure.REASON_FLUSHED:
466467
reason = "The capture has failed due to an abortCaptures() call";
468+
fatalFailure = true;
467469
break;
468470
default:
469471
reason = "Unknown reason";
470472
}
471-
pictureCaptureRequest.error("captureFailure", reason, null);
473+
Log.w("Camera", "pictureCaptureCallback.onCaptureFailed(): " + reason);
474+
if (fatalFailure) pictureCaptureRequest.error("captureFailure", reason, null);
472475
}
473476

474477
private void processCapture(CaptureResult result) {

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package io.flutter.plugins.camera;
66

7+
import android.os.Handler;
8+
import android.os.Looper;
79
import androidx.annotation.Nullable;
810
import io.flutter.plugin.common.MethodChannel;
911

@@ -19,17 +21,36 @@ enum State {
1921
error,
2022
}
2123

24+
private final Runnable timeoutCallback =
25+
new Runnable() {
26+
@Override
27+
public void run() {
28+
error("captureTimeout", "Picture capture request timed out", state.toString());
29+
}
30+
};
31+
2232
private final MethodChannel.Result result;
33+
private final TimeoutHandler timeoutHandler;
2334
private State state;
2435

2536
public PictureCaptureRequest(MethodChannel.Result result) {
37+
this(result, new TimeoutHandler());
38+
}
39+
40+
public PictureCaptureRequest(MethodChannel.Result result, TimeoutHandler timeoutHandler) {
2641
this.result = result;
27-
state = State.idle;
42+
this.state = State.idle;
43+
this.timeoutHandler = timeoutHandler;
2844
}
2945

3046
public void setState(State state) {
3147
if (isFinished()) throw new IllegalStateException("Request has already been finished");
3248
this.state = state;
49+
if (state != State.idle && state != State.finished && state != State.error) {
50+
this.timeoutHandler.resetTimeout(timeoutCallback);
51+
} else {
52+
this.timeoutHandler.clearTimeout(timeoutCallback);
53+
}
3354
}
3455

3556
public State getState() {
@@ -42,14 +63,34 @@ public boolean isFinished() {
4263

4364
public void finish(String absolutePath) {
4465
if (isFinished()) throw new IllegalStateException("Request has already been finished");
66+
this.timeoutHandler.clearTimeout(timeoutCallback);
4567
result.success(absolutePath);
4668
state = State.finished;
4769
}
4870

4971
public void error(
5072
String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
5173
if (isFinished()) throw new IllegalStateException("Request has already been finished");
74+
this.timeoutHandler.clearTimeout(timeoutCallback);
5275
result.error(errorCode, errorMessage, errorDetails);
5376
state = State.error;
5477
}
78+
79+
static class TimeoutHandler {
80+
private static final int REQUEST_TIMEOUT = 5000;
81+
private final Handler handler;
82+
83+
TimeoutHandler() {
84+
this.handler = new Handler(Looper.getMainLooper());
85+
}
86+
87+
public void resetTimeout(Runnable runnable) {
88+
clearTimeout(runnable);
89+
handler.postDelayed(runnable, REQUEST_TIMEOUT);
90+
}
91+
92+
public void clearTimeout(Runnable runnable) {
93+
handler.removeCallbacks(runnable);
94+
}
95+
}
5596
}

packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
import static org.junit.Assert.assertEquals;
88
import static org.junit.Assert.assertFalse;
99
import static org.junit.Assert.assertTrue;
10+
import static org.mockito.ArgumentMatchers.any;
1011
import static org.mockito.Mockito.mock;
12+
import static org.mockito.Mockito.never;
13+
import static org.mockito.Mockito.times;
1114
import static org.mockito.Mockito.verify;
1215

1316
import io.flutter.plugin.common.MethodChannel;
@@ -38,6 +41,32 @@ public void setState_sets_state() {
3841
"State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing);
3942
}
4043

44+
@Test
45+
public void setState_resets_timeout() {
46+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
47+
mock(PictureCaptureRequest.TimeoutHandler.class);
48+
PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler);
49+
req.setState(PictureCaptureRequest.State.focusing);
50+
req.setState(PictureCaptureRequest.State.preCapture);
51+
req.setState(PictureCaptureRequest.State.waitingPreCaptureReady);
52+
req.setState(PictureCaptureRequest.State.capturing);
53+
verify(mockTimeoutHandler, times(4)).resetTimeout(any());
54+
verify(mockTimeoutHandler, never()).clearTimeout(any());
55+
}
56+
57+
@Test
58+
public void setState_clears_timeout() {
59+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
60+
mock(PictureCaptureRequest.TimeoutHandler.class);
61+
PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler);
62+
req.setState(PictureCaptureRequest.State.idle);
63+
req.setState(PictureCaptureRequest.State.finished);
64+
req = new PictureCaptureRequest(null, mockTimeoutHandler);
65+
req.setState(PictureCaptureRequest.State.error);
66+
verify(mockTimeoutHandler, never()).resetTimeout(any());
67+
verify(mockTimeoutHandler, times(3)).clearTimeout(any());
68+
}
69+
4170
@Test
4271
public void finish_sets_result_and_state() {
4372
// Setup
@@ -50,6 +79,17 @@ public void finish_sets_result_and_state() {
5079
assertEquals("State is finished", req.getState(), PictureCaptureRequest.State.finished);
5180
}
5281

82+
@Test
83+
public void finish_clears_timeout() {
84+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
85+
mock(PictureCaptureRequest.TimeoutHandler.class);
86+
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
87+
PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler);
88+
req.finish("/test/path");
89+
verify(mockTimeoutHandler, never()).resetTimeout(any());
90+
verify(mockTimeoutHandler).clearTimeout(any());
91+
}
92+
5393
@Test
5494
public void isFinished_is_true_When_state_is_finished_or_error() {
5595
// Setup
@@ -90,6 +130,17 @@ public void error_sets_result_and_state() {
90130
assertEquals("State is error", req.getState(), PictureCaptureRequest.State.error);
91131
}
92132

133+
@Test
134+
public void error_clears_timeout() {
135+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
136+
mock(PictureCaptureRequest.TimeoutHandler.class);
137+
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
138+
PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler);
139+
req.error("ERROR_CODE", "Error Message", null);
140+
verify(mockTimeoutHandler, never()).resetTimeout(any());
141+
verify(mockTimeoutHandler).clearTimeout(any());
142+
}
143+
93144
@Test(expected = IllegalStateException.class)
94145
public void error_throws_When_already_finished() {
95146
// Setup

packages/camera/camera/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera
22
description: A Flutter plugin for getting information about and controlling the
33
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
44
and streaming image buffers to dart.
5-
version: 0.7.0
5+
version: 0.7.0+1
66
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
77

88
dependencies:

0 commit comments

Comments
 (0)