Skip to content

Commit b55f2e5

Browse files
authored
Merge pull request flutter#5 from bottlepay/android-rework-extract-capture-callback
Android rework extract capture callback
2 parents 25cff82 + e7808a5 commit b55f2e5

9 files changed

Lines changed: 304 additions & 219 deletions

File tree

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

Lines changed: 23 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ interface ErrorCallback {
6868
void onError(String errorCode, String errorMessage);
6969
}
7070

71-
public class Camera {
71+
class Camera implements CameraCaptureCallback.CameraCaptureStateListener {
7272
private static final String TAG = "Camera";
7373

7474
/** Conversion from screen rotation to JPEG orientation. */
@@ -92,7 +92,6 @@ public class Camera {
9292

9393
private final SurfaceTextureEntry flutterTexture;
9494
private final DeviceOrientationManager deviceOrientationListener;
95-
private final boolean isFrontFacing;
9695
private final int sensorOrientation;
9796
private final Size captureSize;
9897
private final Size previewSize;
@@ -166,88 +165,7 @@ public void onImageAvailable(ImageReader reader) {
166165
private CameraRegions cameraRegions;
167166
private int exposureOffset;
168167
/** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */
169-
private final CameraCaptureSession.CaptureCallback mCaptureCallback =
170-
new CameraCaptureSession.CaptureCallback() {
171-
172-
private void process(CaptureResult result) {
173-
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
174-
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
175-
176-
if (cameraState != CameraState.STATE_PREVIEW) {
177-
// Log.i(TAG, "mCaptureCallback | state: " + cameraState + " | afState: " + afState + " | aeState: " + aeState);
178-
}
179-
180-
switch (cameraState) {
181-
case STATE_PREVIEW:
182-
{
183-
// We have nothing to do when the camera preview is working normally.
184-
break;
185-
}
186-
187-
case STATE_WAITING_FOCUS:
188-
{
189-
if (afState == null) {
190-
return;
191-
} else if (afState == CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN
192-
|| afState == CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED
193-
|| afState == CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
194-
// CONTROL_AE_STATE can be null on some devices
195-
196-
if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
197-
takePictureAfterPrecapture();
198-
} else {
199-
runPrecaptureSequence();
200-
}
201-
}
202-
break;
203-
}
204-
205-
case STATE_WAITING_PRECAPTURE_START:
206-
{
207-
// CONTROL_AE_STATE can be null on some devices
208-
if (aeState == null
209-
|| aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
210-
|| aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE
211-
|| aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) {
212-
cameraState = CameraState.STATE_WAITING_PRECAPTURE_DONE;
213-
pictureCaptureRequest.setState(
214-
PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_DONE);
215-
}
216-
break;
217-
}
218-
219-
case STATE_WAITING_PRECAPTURE_DONE:
220-
{
221-
// CONTROL_AE_STATE can be null on some devices
222-
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
223-
takePictureAfterPrecapture();
224-
} else {
225-
if (pictureCaptureRequest.hitPreCaptureTimeout()) {
226-
// Log.i(TAG, "===> Hit precapture timeout");
227-
unlockAutoFocus();
228-
}
229-
}
230-
break;
231-
}
232-
}
233-
}
234-
235-
@Override
236-
public void onCaptureProgressed(
237-
@NonNull CameraCaptureSession session,
238-
@NonNull CaptureRequest request,
239-
@NonNull CaptureResult partialResult) {
240-
process(partialResult);
241-
}
242-
243-
@Override
244-
public void onCaptureCompleted(
245-
@NonNull CameraCaptureSession session,
246-
@NonNull CaptureRequest request,
247-
@NonNull TotalCaptureResult result) {
248-
process(result);
249-
}
250-
};
168+
private final CameraCaptureCallback mCaptureCallback;
251169

252170
private Range<Integer> fpsRange;
253171
private PlatformChannel.DeviceOrientation lockedCaptureOrientation;
@@ -259,24 +177,6 @@ public Camera(
259177
final CameraProperties cameraProperties,
260178
final ResolutionPreset resolutionPreset,
261179
final boolean enableAudio) {
262-
this(
263-
activity,
264-
flutterTexture,
265-
dartMessenger,
266-
cameraProperties,
267-
resolutionPreset,
268-
enableAudio,
269-
null);
270-
}
271-
272-
public Camera(
273-
final Activity activity,
274-
final SurfaceTextureEntry flutterTexture,
275-
final DartMessenger dartMessenger,
276-
final CameraProperties cameraProperties,
277-
final ResolutionPreset resolutionPreset,
278-
final boolean enableAudio,
279-
@Nullable final DeviceOrientationManager deviceOrientationManager) {
280180

281181
if (activity == null) {
282182
throw new IllegalStateException("No activity available!");
@@ -293,19 +193,18 @@ public Camera(
293193
this.currentFocusMode = FocusMode.auto;
294194
this.exposureOffset = 0;
295195

196+
mCaptureCallback = CameraCaptureCallback.create(this);
197+
296198
// Get camera characteristics and check for supported features
297199
getAvailableFpsRange(cameraProperties);
298200
mAutoFocusSupported = checkAutoFocusSupported(cameraProperties);
299201
checkFlashSupported();
300202

301203
// Setup orientation
302204
sensorOrientation = cameraProperties.getSensorOrientation();
303-
isFrontFacing = cameraProperties.getLensFacing() == CameraMetadata.LENS_FACING_FRONT;
205+
boolean isFrontFacing = cameraProperties.getLensFacing() == CameraMetadata.LENS_FACING_FRONT;
304206

305-
deviceOrientationListener =
306-
deviceOrientationManager != null
307-
? deviceOrientationManager
308-
: new DeviceOrientationManager(
207+
deviceOrientationListener = DeviceOrientationManager.create(
309208
activity, dartMessenger, isFrontFacing, sensorOrientation);
310209
deviceOrientationListener.start();
311210

@@ -330,6 +229,21 @@ public Camera(
330229
startBackgroundThread();
331230
}
332231

232+
@Override
233+
public void onConverged() {
234+
takePictureAfterPrecapture();
235+
}
236+
237+
@Override
238+
public void onPrecapture() {
239+
runPrecaptureSequence();
240+
}
241+
242+
@Override
243+
public void onPrecaptureTimeout() {
244+
unlockAutoFocus();
245+
}
246+
333247
/** Get the current camera state (use for testing). */
334248
public CameraState getState() {
335249
return this.cameraState;
@@ -642,7 +556,8 @@ public void takePicture(@NonNull final Result result) {
642556
final File file = File.createTempFile("CAP", ".jpg", outputDir);
643557

644558
// Start a new capture
645-
pictureCaptureRequest = new PictureCaptureRequest(result, file, dartMessenger);
559+
pictureCaptureRequest = PictureCaptureRequest.create(result, file, dartMessenger);
560+
mCaptureCallback.setPictureCaptureRequest(pictureCaptureRequest);
646561
} catch (IOException | SecurityException e) {
647562
pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null);
648563
return;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package io.flutter.plugins.camera;
2+
3+
import android.hardware.camera2.CameraCaptureSession;
4+
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
5+
import android.hardware.camera2.CaptureRequest;
6+
import android.hardware.camera2.CaptureResult;
7+
import android.hardware.camera2.TotalCaptureResult;
8+
import androidx.annotation.NonNull;
9+
10+
class CameraCaptureCallback extends CaptureCallback {
11+
interface CameraCaptureStateListener {
12+
void onConverged();
13+
void onPrecapture();
14+
void onPrecaptureTimeout();
15+
}
16+
17+
private final CameraCaptureStateListener cameraStateListener;
18+
19+
private CameraState cameraState;
20+
private PictureCaptureRequest pictureCaptureRequest;
21+
22+
public static CameraCaptureCallback create(
23+
@NonNull CameraCaptureStateListener cameraStateListener) {
24+
return new CameraCaptureCallback(cameraStateListener);
25+
}
26+
27+
private CameraCaptureCallback(
28+
@NonNull CameraCaptureStateListener cameraStateListener) {
29+
cameraState = CameraState.STATE_PREVIEW;
30+
this.cameraStateListener = cameraStateListener;
31+
this.pictureCaptureRequest = pictureCaptureRequest;
32+
}
33+
34+
public CameraState getCameraState() {
35+
return cameraState;
36+
}
37+
38+
public void setCameraState(@NonNull CameraState state) {
39+
cameraState = state;
40+
41+
if (pictureCaptureRequest != null && state == CameraState.STATE_WAITING_PRECAPTURE_DONE) {
42+
pictureCaptureRequest.setState(
43+
PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_DONE);
44+
}
45+
}
46+
47+
public void setPictureCaptureRequest(@NonNull PictureCaptureRequest pictureCaptureRequest) {
48+
this.pictureCaptureRequest = pictureCaptureRequest;
49+
}
50+
51+
private void process(CaptureResult result) {
52+
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
53+
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
54+
55+
switch (cameraState) {
56+
case STATE_PREVIEW:
57+
{
58+
// We have nothing to do when the camera preview is working normally.
59+
break;
60+
}
61+
case STATE_WAITING_FOCUS:
62+
{
63+
if (afState == null) {
64+
return;
65+
} else if (afState == CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN
66+
|| afState == CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED
67+
|| afState == CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
68+
// CONTROL_AE_STATE can be null on some devices
69+
70+
if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
71+
cameraStateListener.onConverged();
72+
} else {
73+
cameraStateListener.onPrecapture();
74+
}
75+
}
76+
break;
77+
}
78+
79+
case STATE_WAITING_PRECAPTURE_START:
80+
{
81+
// CONTROL_AE_STATE can be null on some devices
82+
if (aeState == null
83+
|| aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
84+
|| aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE
85+
|| aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) {
86+
setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE);
87+
}
88+
break;
89+
}
90+
91+
case STATE_WAITING_PRECAPTURE_DONE:
92+
{
93+
// CONTROL_AE_STATE can be null on some devices
94+
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
95+
cameraStateListener.onConverged();
96+
} else if (pictureCaptureRequest != null && pictureCaptureRequest.hitPreCaptureTimeout()) {
97+
// Log.i(TAG, "===> Hit precapture timeout");
98+
cameraStateListener.onPrecaptureTimeout();
99+
}
100+
break;
101+
}
102+
}
103+
}
104+
105+
@Override
106+
public void onCaptureProgressed(
107+
@NonNull CameraCaptureSession session,
108+
@NonNull CaptureRequest request,
109+
@NonNull CaptureResult partialResult) {
110+
process(partialResult);
111+
}
112+
113+
@Override
114+
public void onCaptureCompleted(
115+
@NonNull CameraCaptureSession session,
116+
@NonNull CaptureRequest request,
117+
@NonNull TotalCaptureResult result) {
118+
process(result);
119+
}
120+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ enum CameraEventType {
4141
}
4242
}
4343

44-
DartMessenger(BinaryMessenger messenger, long cameraId) {
44+
public DartMessenger(BinaryMessenger messenger, long cameraId) {
4545
cameraChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId);
4646
deviceChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/device");
4747
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@ class DeviceOrientationManager {
3131
private OrientationEventListener orientationEventListener;
3232
private BroadcastReceiver broadcastReceiver;
3333

34-
public DeviceOrientationManager(
34+
/**
35+
* Factory method to create a device orientation manager.
36+
*/
37+
public static DeviceOrientationManager create(
38+
Activity activity,
39+
DartMessenger messenger,
40+
boolean isFrontFacing,
41+
int sensorOrientation
42+
) {
43+
return new DeviceOrientationManager(activity, messenger, isFrontFacing, sensorOrientation);
44+
}
45+
46+
private DeviceOrientationManager(
3547
Activity activity, DartMessenger messenger, boolean isFrontFacing, int sensorOrientation) {
3648
this.activity = activity;
3749
this.messenger = messenger;

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,33 +51,33 @@ class PictureCaptureRequest {
5151
};
5252

5353
/**
54-
* Constructor to create a picture capture request.
54+
* Factory method to create a picture capture request.
5555
*
5656
* @param result
5757
* @param file
5858
*/
59-
public PictureCaptureRequest(
59+
static PictureCaptureRequest create(
6060
MethodChannel.Result result,
6161
File file,
6262
DartMessenger dartMessenger) {
63-
this(
64-
result,
65-
file,
66-
dartMessenger,
67-
new TimeoutHandler()
68-
);
63+
return new PictureCaptureRequest(result, file, dartMessenger);
6964
}
7065

71-
/** Constructor for unit tests where we can mock the timeout handler */
72-
public PictureCaptureRequest(
66+
/**
67+
* Private constructor to create a picture capture request.
68+
*
69+
* @param result
70+
* @param file
71+
*/
72+
private PictureCaptureRequest(
7373
MethodChannel.Result result,
7474
File file,
75-
DartMessenger dartMessenger,
76-
TimeoutHandler timeoutHandler) {
75+
DartMessenger dartMessenger) {
76+
7777
this.result = result;
78-
this.timeoutHandler = timeoutHandler;
7978
this.file = file;
8079
this.dartMessenger = dartMessenger;
80+
this.timeoutHandler = TimeoutHandler.create();
8181
}
8282

8383
/**
@@ -184,7 +184,11 @@ static class TimeoutHandler {
184184
private static final int REQUEST_TIMEOUT = 5000;
185185
private final Handler handler;
186186

187-
TimeoutHandler() {
187+
public static TimeoutHandler create() {
188+
return new TimeoutHandler();
189+
}
190+
191+
private TimeoutHandler() {
188192
this.handler = new Handler(Looper.getMainLooper());
189193
}
190194

0 commit comments

Comments
 (0)