Skip to content

Commit 0f92c7f

Browse files
BeMacizedAperico-com
authored andcommitted
[camera] Fix picture capture causing a crash on some Huawei devices. (flutter#3414)
1 parent da1b463 commit 0f92c7f

File tree

5 files changed

+105
-5
lines changed

5 files changed

+105
-5
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.6.5+1
2+
3+
* Fixes picture captures causing a crash on some Huawei devices.
4+
5+
16
## 0.6.5
27

38
* Adds ImageFormat selection for ImageStream and Video(iOS only).
@@ -15,7 +20,7 @@
1520

1621
## 0.6.4+2
1722

18-
* Set ImageStreamReader listener to null to prevent stale images when streaming images.
23+
* Set ImageStreamReader listener to null to prevent stale images when streaming images.
1924

2025
## 0.6.4+1
2126

@@ -75,7 +80,7 @@ As part of implementing federated architecture and making the interface compatib
7580

7681
Method changes in `CameraController`:
7782
- The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class;
78-
- The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes;
83+
- The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes;
7984
- The `stopVideoRecording` method now returns the captured video when it completes;
8085
- Added the `buildPreview` method which is now used to implement the CameraPreview widget.
8186

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
@@ -455,17 +455,20 @@ public void onCaptureFailed(
455455
return;
456456
}
457457
String reason;
458+
boolean fatalFailure = false;
458459
switch (failure.getReason()) {
459460
case CaptureFailure.REASON_ERROR:
460461
reason = "An error happened in the framework";
461462
break;
462463
case CaptureFailure.REASON_FLUSHED:
463464
reason = "The capture has failed due to an abortCaptures() call";
465+
fatalFailure = true;
464466
break;
465467
default:
466468
reason = "Unknown reason";
467469
}
468-
pictureCaptureRequest.error("captureFailure", reason, null);
470+
Log.w("Camera", "pictureCaptureCallback.onCaptureFailed(): " + reason);
471+
if (fatalFailure) pictureCaptureRequest.error("captureFailure", reason, null);
469472
}
470473

471474
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
@@ -1,5 +1,7 @@
11
package io.flutter.plugins.camera;
22

3+
import android.os.Handler;
4+
import android.os.Looper;
35
import androidx.annotation.Nullable;
46
import io.flutter.plugin.common.MethodChannel;
57

@@ -15,17 +17,36 @@ enum State {
1517
error,
1618
}
1719

20+
private final Runnable timeoutCallback =
21+
new Runnable() {
22+
@Override
23+
public void run() {
24+
error("captureTimeout", "Picture capture request timed out", state.toString());
25+
}
26+
};
27+
1828
private final MethodChannel.Result result;
29+
private final TimeoutHandler timeoutHandler;
1930
private State state;
2031

2132
public PictureCaptureRequest(MethodChannel.Result result) {
33+
this(result, new TimeoutHandler());
34+
}
35+
36+
public PictureCaptureRequest(MethodChannel.Result result, TimeoutHandler timeoutHandler) {
2237
this.result = result;
23-
state = State.idle;
38+
this.state = State.idle;
39+
this.timeoutHandler = timeoutHandler;
2440
}
2541

2642
public void setState(State state) {
2743
if (isFinished()) throw new IllegalStateException("Request has already been finished");
2844
this.state = state;
45+
if (state != State.idle && state != State.finished && state != State.error) {
46+
this.timeoutHandler.resetTimeout(timeoutCallback);
47+
} else {
48+
this.timeoutHandler.clearTimeout(timeoutCallback);
49+
}
2950
}
3051

3152
public State getState() {
@@ -38,14 +59,34 @@ public boolean isFinished() {
3859

3960
public void finish(String absolutePath) {
4061
if (isFinished()) throw new IllegalStateException("Request has already been finished");
62+
this.timeoutHandler.clearTimeout(timeoutCallback);
4163
result.success(absolutePath);
4264
state = State.finished;
4365
}
4466

4567
public void error(
4668
String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
4769
if (isFinished()) throw new IllegalStateException("Request has already been finished");
70+
this.timeoutHandler.clearTimeout(timeoutCallback);
4871
result.error(errorCode, errorMessage, errorDetails);
4972
state = State.error;
5073
}
74+
75+
static class TimeoutHandler {
76+
private static final int REQUEST_TIMEOUT = 5000;
77+
private final Handler handler;
78+
79+
TimeoutHandler() {
80+
this.handler = new Handler(Looper.getMainLooper());
81+
}
82+
83+
public void resetTimeout(Runnable runnable) {
84+
clearTimeout(runnable);
85+
handler.postDelayed(runnable, REQUEST_TIMEOUT);
86+
}
87+
88+
public void clearTimeout(Runnable runnable) {
89+
handler.removeCallbacks(runnable);
90+
}
91+
}
5192
}

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
@@ -3,7 +3,10 @@
33
import static org.junit.Assert.assertEquals;
44
import static org.junit.Assert.assertFalse;
55
import static org.junit.Assert.assertTrue;
6+
import static org.mockito.ArgumentMatchers.any;
67
import static org.mockito.Mockito.mock;
8+
import static org.mockito.Mockito.never;
9+
import static org.mockito.Mockito.times;
710
import static org.mockito.Mockito.verify;
811

912
import io.flutter.plugin.common.MethodChannel;
@@ -34,6 +37,32 @@ public void setState_sets_state() {
3437
"State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing);
3538
}
3639

40+
@Test
41+
public void setState_resets_timeout() {
42+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
43+
mock(PictureCaptureRequest.TimeoutHandler.class);
44+
PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler);
45+
req.setState(PictureCaptureRequest.State.focusing);
46+
req.setState(PictureCaptureRequest.State.preCapture);
47+
req.setState(PictureCaptureRequest.State.waitingPreCaptureReady);
48+
req.setState(PictureCaptureRequest.State.capturing);
49+
verify(mockTimeoutHandler, times(4)).resetTimeout(any());
50+
verify(mockTimeoutHandler, never()).clearTimeout(any());
51+
}
52+
53+
@Test
54+
public void setState_clears_timeout() {
55+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
56+
mock(PictureCaptureRequest.TimeoutHandler.class);
57+
PictureCaptureRequest req = new PictureCaptureRequest(null, mockTimeoutHandler);
58+
req.setState(PictureCaptureRequest.State.idle);
59+
req.setState(PictureCaptureRequest.State.finished);
60+
req = new PictureCaptureRequest(null, mockTimeoutHandler);
61+
req.setState(PictureCaptureRequest.State.error);
62+
verify(mockTimeoutHandler, never()).resetTimeout(any());
63+
verify(mockTimeoutHandler, times(3)).clearTimeout(any());
64+
}
65+
3766
@Test
3867
public void finish_sets_result_and_state() {
3968
// Setup
@@ -46,6 +75,17 @@ public void finish_sets_result_and_state() {
4675
assertEquals("State is finished", req.getState(), PictureCaptureRequest.State.finished);
4776
}
4877

78+
@Test
79+
public void finish_clears_timeout() {
80+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
81+
mock(PictureCaptureRequest.TimeoutHandler.class);
82+
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
83+
PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler);
84+
req.finish("/test/path");
85+
verify(mockTimeoutHandler, never()).resetTimeout(any());
86+
verify(mockTimeoutHandler).clearTimeout(any());
87+
}
88+
4989
@Test
5090
public void isFinished_is_true_When_state_is_finished_or_error() {
5191
// Setup
@@ -86,6 +126,17 @@ public void error_sets_result_and_state() {
86126
assertEquals("State is error", req.getState(), PictureCaptureRequest.State.error);
87127
}
88128

129+
@Test
130+
public void error_clears_timeout() {
131+
PictureCaptureRequest.TimeoutHandler mockTimeoutHandler =
132+
mock(PictureCaptureRequest.TimeoutHandler.class);
133+
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
134+
PictureCaptureRequest req = new PictureCaptureRequest(mockResult, mockTimeoutHandler);
135+
req.error("ERROR_CODE", "Error Message", null);
136+
verify(mockTimeoutHandler, never()).resetTimeout(any());
137+
verify(mockTimeoutHandler).clearTimeout(any());
138+
}
139+
89140
@Test(expected = IllegalStateException.class)
90141
public void error_throws_When_already_finished() {
91142
// 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.6.5
5+
version: 0.6.5+1
66
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
77

88
dependencies:

0 commit comments

Comments
 (0)