Skip to content

Commit c62f6f7

Browse files
authored
[camerax] Implement onCameraClosing (#3878)
### Overview Continuation of #3419. Implements onCameraClosing that will send an event anytime the camera closes, as detected by observing the LiveData of the CameraState of the current camera being used in the plugin. Fixes flutter/flutter#121162. ### Highlights of what has changed since the latest reviews of the [last PR](#3419): - Wrapped `LiveData#getValue` ([docs](https://developer.android.com/reference/androidx/lifecycle/LiveData#getValue())) for the purposes of retrieving current value `LiveData<ZoomState>` to properly implement retrieving zoom information - Modified Dart Host API and FlutterAPI implementations' documentation on their `binaryMessenger` and `instanceManager` documentation to be clearer as per suggestion from comments on the original PR - Changed `binaryMessenger` and `instanceManager` fields to be private in Flutter API implementations - Removed `cast` method and added enum `LiveDataSupportedType` to pigeon file to use for safely casting the `LiveData` generic when received on the Dart or native side. - Moved `CameraStateError` code decoding to Dart side as this was written by me and not directly provided by CameraX. - Added tests for the conversion of objects. - The following issues were filed to capture feedback given but not addressed in this PR: flutter/flutter#125926, flutter/flutter#125928.
1 parent fd7c3cd commit c62f6f7

File tree

63 files changed

+4250
-362
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4250
-362
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@
2323
* Implements retrieval of camera information.
2424
* Updates README.md with plugin overview and adds contribution guide to CONTRIBUTING.md.
2525
* Implements video capture.
26+
* Implements onCameraClosing callback method for indicating the camera is closing and bumps CameraX version to 1.3.0-alpha05.
27+

packages/camera/camera_android_camerax/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ android {
6262

6363
dependencies {
6464
// CameraX core library using the camera2 implementation must use same version number.
65-
def camerax_version = "1.3.0-alpha04"
65+
def camerax_version = "1.3.0-alpha05"
6666
implementation "androidx.camera:camera-core:${camerax_version}"
6767
implementation "androidx.camera:camera-camera2:${camerax_version}"
6868
implementation "androidx.camera:camera-lifecycle:${camerax_version}"

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
2626
private ImageCaptureHostApiImpl imageCaptureHostApiImpl;
2727
public SystemServicesHostApiImpl systemServicesHostApiImpl;
2828

29-
@VisibleForTesting ProcessCameraProviderHostApiImpl processCameraProviderHostApiImpl;
29+
@VisibleForTesting @NonNull
30+
public ProcessCameraProviderHostApiImpl processCameraProviderHostApiImpl;
31+
32+
@VisibleForTesting @NonNull public LiveDataHostApiImpl liveDataHostApiImpl;
3033

3134
/**
3235
* Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment.
@@ -71,6 +74,12 @@ public void setUp(
7174
imageCaptureHostApiImpl =
7275
new ImageCaptureHostApiImpl(binaryMessenger, instanceManager, context);
7376
GeneratedCameraXLibrary.ImageCaptureHostApi.setup(binaryMessenger, imageCaptureHostApiImpl);
77+
GeneratedCameraXLibrary.CameraHostApi.setup(
78+
binaryMessenger, new CameraHostApiImpl(binaryMessenger, instanceManager));
79+
liveDataHostApiImpl = new LiveDataHostApiImpl(binaryMessenger, instanceManager);
80+
GeneratedCameraXLibrary.LiveDataHostApi.setup(binaryMessenger, liveDataHostApiImpl);
81+
GeneratedCameraXLibrary.ObserverHostApi.setup(
82+
binaryMessenger, new ObserverHostApiImpl(binaryMessenger, instanceManager));
7483
imageAnalysisHostApiImpl = new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager);
7584
GeneratedCameraXLibrary.ImageAnalysisHostApi.setup(binaryMessenger, imageAnalysisHostApiImpl);
7685
GeneratedCameraXLibrary.AnalyzerHostApi.setup(
@@ -105,19 +114,18 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
105114

106115
@Override
107116
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
108-
setUp(
109-
pluginBinding.getBinaryMessenger(),
110-
pluginBinding.getApplicationContext(),
111-
pluginBinding.getTextureRegistry());
112-
updateContext(pluginBinding.getApplicationContext());
113-
114117
Activity activity = activityPluginBinding.getActivity();
115118

119+
setUp(pluginBinding.getBinaryMessenger(), activity, pluginBinding.getTextureRegistry());
120+
updateContext(activity);
121+
116122
if (activity instanceof LifecycleOwner) {
117123
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
124+
liveDataHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
118125
} else {
119126
ProxyLifecycleProvider proxyLifecycleProvider = new ProxyLifecycleProvider(activity);
120127
processCameraProviderHostApiImpl.setLifecycleOwner(proxyLifecycleProvider);
128+
liveDataHostApiImpl.setLifecycleOwner(proxyLifecycleProvider);
121129
}
122130

123131
systemServicesHostApiImpl.setActivity(activity);

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraHostApiImpl.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ public Long getCameraInfo(@NonNull Long identifier) {
3131
Camera camera = (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier));
3232
CameraInfo cameraInfo = camera.getCameraInfo();
3333

34-
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
35-
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
36-
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
37-
34+
if (!instanceManager.containsInstance(cameraInfo)) {
35+
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
36+
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
37+
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
38+
}
3839
return instanceManager.getIdentifierForStrongReference(cameraInfo);
3940
}
4041
}

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoFlutterApiImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public CameraInfoFlutterApiImpl(
1717
this.instanceManager = instanceManager;
1818
}
1919

20+
/**
21+
* Creates a {@link CameraInfo} instance in Dart. {@code reply} is not used so it can be empty.
22+
*/
2023
void create(CameraInfo cameraInfo, Reply<Void> reply) {
2124
if (!instanceManager.containsInstance(cameraInfo)) {
2225
create(instanceManager.addHostCreatedInstance(cameraInfo), reply);

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,35 @@
55
package io.flutter.plugins.camerax;
66

77
import androidx.annotation.NonNull;
8+
import androidx.annotation.VisibleForTesting;
89
import androidx.camera.core.CameraInfo;
10+
import androidx.camera.core.CameraState;
911
import androidx.camera.core.ExposureState;
1012
import androidx.camera.core.ZoomState;
13+
import androidx.lifecycle.LiveData;
1114
import io.flutter.plugin.common.BinaryMessenger;
1215
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraInfoHostApi;
16+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.LiveDataSupportedType;
1317
import java.util.Objects;
1418

1519
public class CameraInfoHostApiImpl implements CameraInfoHostApi {
1620
private final BinaryMessenger binaryMessenger;
1721
private final InstanceManager instanceManager;
1822

23+
@VisibleForTesting public LiveDataFlutterApiWrapper liveDataFlutterApiWrapper;
24+
1925
public CameraInfoHostApiImpl(
2026
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
2127
this.binaryMessenger = binaryMessenger;
2228
this.instanceManager = instanceManager;
29+
this.liveDataFlutterApiWrapper =
30+
new LiveDataFlutterApiWrapper(binaryMessenger, instanceManager);
2331
}
2432

25-
/** Retrieves the sensor rotation in degrees, relative to the device's default orientation. */
33+
/**
34+
* Retrieves the sensor rotation degrees of the {@link androidx.camera.core.Camera} that is
35+
* represented by the {@link CameraInfo} with the specified identifier.
36+
*/
2637
@Override
2738
@NonNull
2839
public Long getSensorRotationDegrees(@NonNull Long identifier) {
@@ -31,6 +42,22 @@ public Long getSensorRotationDegrees(@NonNull Long identifier) {
3142
return Long.valueOf(cameraInfo.getSensorRotationDegrees());
3243
}
3344

45+
/**
46+
* Retrieves the {@link LiveData} of the {@link CameraState} that is tied to the {@link
47+
* androidx.camera.core.Camera} that is represented by the {@link CameraInfo} with the specified
48+
* identifier.
49+
*/
50+
@Override
51+
@NonNull
52+
public Long getCameraState(@NonNull Long identifier) {
53+
CameraInfo cameraInfo =
54+
(CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier));
55+
LiveData<CameraState> liveCameraState = cameraInfo.getCameraState();
56+
liveDataFlutterApiWrapper.create(
57+
liveCameraState, LiveDataSupportedType.CAMERA_STATE, reply -> {});
58+
return instanceManager.getIdentifierForStrongReference(liveCameraState);
59+
}
60+
3461
/**
3562
* Retrieves the {@link ExposureState} of the {@link CameraInfo} with the specified identifier.
3663
*/
@@ -49,20 +76,19 @@ public Long getExposureState(@NonNull Long identifier) {
4976
}
5077

5178
/**
52-
* Retrieves the current {@link ZoomState} value of the {@link CameraInfo} with the specified
53-
* identifier.
79+
* Retrieves the {@link LiveData} of the {@link ZoomState} of the {@link CameraInfo} with the
80+
* specified identifier.
5481
*/
5582
@NonNull
5683
@Override
5784
public Long getZoomState(@NonNull Long identifier) {
5885
CameraInfo cameraInfo =
5986
(CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier));
60-
// TODO(camsim99): Create/return LiveData<ZoomState> once https://github.com/flutter/packages/pull/3419 lands.
61-
ZoomState zoomState = cameraInfo.getZoomState().getValue();
87+
LiveData<ZoomState> zoomState = cameraInfo.getZoomState();
6288

63-
ZoomStateFlutterApiImpl zoomStateFlutterApiImpl =
64-
new ZoomStateFlutterApiImpl(binaryMessenger, instanceManager);
65-
zoomStateFlutterApiImpl.create(zoomState, result -> {});
89+
LiveDataFlutterApiWrapper liveDataFlutterApiWrapper =
90+
new LiveDataFlutterApiWrapper(binaryMessenger, instanceManager);
91+
liveDataFlutterApiWrapper.create(zoomState, LiveDataSupportedType.ZOOM_STATE, reply -> {});
6692

6793
return instanceManager.getIdentifierForStrongReference(zoomState);
6894
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camerax;
6+
7+
import androidx.annotation.NonNull;
8+
import androidx.annotation.VisibleForTesting;
9+
import androidx.camera.core.CameraState;
10+
import io.flutter.plugin.common.BinaryMessenger;
11+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateErrorFlutterApi;
12+
13+
/**
14+
* Flutter API implementation for {@link CameraStateError}.
15+
*
16+
* <p>This class may handle adding native instances that are attached to a Dart instance or passing
17+
* arguments of callbacks methods to a Dart instance.
18+
*/
19+
public class CameraStateErrorFlutterApiWrapper {
20+
private final BinaryMessenger binaryMessenger;
21+
private final InstanceManager instanceManager;
22+
private CameraStateErrorFlutterApi cameraStateErrorFlutterApi;
23+
24+
/**
25+
* Constructs a {@link CameraStateErrorFlutterApiWrapper}.
26+
*
27+
* @param binaryMessenger used to communicate with Dart over asynchronous messages
28+
* @param instanceManager maintains instances stored to communicate with attached Dart objects
29+
*/
30+
public CameraStateErrorFlutterApiWrapper(
31+
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
32+
this.binaryMessenger = binaryMessenger;
33+
this.instanceManager = instanceManager;
34+
cameraStateErrorFlutterApi = new CameraStateErrorFlutterApi(binaryMessenger);
35+
}
36+
37+
/**
38+
* Stores the {@link CameraStateError} instance and notifies Dart to create and store a new {@link
39+
* CameraStateError} instance that is attached to this one. If {@code instance} has already been
40+
* added, this method does nothing.
41+
*/
42+
public void create(
43+
@NonNull CameraState.StateError instance,
44+
@NonNull Long code,
45+
@NonNull CameraStateErrorFlutterApi.Reply<Void> callback) {
46+
if (!instanceManager.containsInstance(instance)) {
47+
cameraStateErrorFlutterApi.create(
48+
instanceManager.addHostCreatedInstance(instance), code, callback);
49+
}
50+
}
51+
52+
/** Sets the Flutter API used to send messages to Dart. */
53+
@VisibleForTesting
54+
void setApi(@NonNull CameraStateErrorFlutterApi api) {
55+
this.cameraStateErrorFlutterApi = api;
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camerax;
6+
7+
import androidx.annotation.NonNull;
8+
import androidx.annotation.Nullable;
9+
import androidx.annotation.VisibleForTesting;
10+
import androidx.camera.core.CameraState;
11+
import io.flutter.plugin.common.BinaryMessenger;
12+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateFlutterApi;
13+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateType;
14+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateTypeData;
15+
16+
/**
17+
* Flutter API implementation for {@link CameraState}.
18+
*
19+
* <p>This class may handle adding native instances that are attached to a Dart instance or passing
20+
* arguments of callbacks methods to a Dart instance.
21+
*/
22+
public class CameraStateFlutterApiWrapper {
23+
private final BinaryMessenger binaryMessenger;
24+
private final InstanceManager instanceManager;
25+
private CameraStateFlutterApi cameraStateFlutterApi;
26+
27+
/**
28+
* Constructs a {@link CameraStateFlutterApiWrapper}.
29+
*
30+
* @param binaryMessenger used to communicate with Dart over asynchronous messages
31+
* @param instanceManager maintains instances stored to communicate with attached Dart objects
32+
*/
33+
public CameraStateFlutterApiWrapper(
34+
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
35+
this.binaryMessenger = binaryMessenger;
36+
this.instanceManager = instanceManager;
37+
cameraStateFlutterApi = new CameraStateFlutterApi(binaryMessenger);
38+
}
39+
40+
/**
41+
* Stores the {@link CameraState} instance and notifies Dart to create and store a new {@link
42+
* CameraState} instance that is attached to this one. If {@code instance} has already been added,
43+
* this method does nothing.
44+
*/
45+
public void create(
46+
@NonNull CameraState instance,
47+
@NonNull CameraStateType type,
48+
@Nullable CameraState.StateError error,
49+
@NonNull CameraStateFlutterApi.Reply<Void> callback) {
50+
if (instanceManager.containsInstance(instance)) {
51+
return;
52+
}
53+
54+
if (error != null) {
55+
// if there is a problem with the current camera state, we need to create a CameraStateError
56+
// to send to the Dart side.
57+
new CameraStateErrorFlutterApiWrapper(binaryMessenger, instanceManager)
58+
.create(error, Long.valueOf(error.getCode()), reply -> {});
59+
}
60+
61+
cameraStateFlutterApi.create(
62+
instanceManager.addHostCreatedInstance(instance),
63+
new CameraStateTypeData.Builder().setValue(type).build(),
64+
instanceManager.getIdentifierForStrongReference(error),
65+
callback);
66+
}
67+
68+
/** Converts CameraX CameraState.Type to CameraStateType that the Dart side understands. */
69+
@NonNull
70+
public static CameraStateType getCameraStateType(@NonNull CameraState.Type type) {
71+
CameraStateType cameraStateType = null;
72+
switch (type) {
73+
case CLOSED:
74+
cameraStateType = CameraStateType.CLOSED;
75+
break;
76+
case CLOSING:
77+
cameraStateType = CameraStateType.CLOSING;
78+
break;
79+
case OPEN:
80+
cameraStateType = CameraStateType.OPEN;
81+
break;
82+
case OPENING:
83+
cameraStateType = CameraStateType.OPENING;
84+
break;
85+
case PENDING_OPEN:
86+
cameraStateType = CameraStateType.PENDING_OPEN;
87+
break;
88+
}
89+
90+
if (cameraStateType == null) {
91+
throw new IllegalArgumentException(
92+
"The CameraState.Type passed to this method was not recognized.");
93+
}
94+
return cameraStateType;
95+
}
96+
97+
/** Sets the Flutter API used to send messages to Dart. */
98+
@VisibleForTesting
99+
void setApi(@NonNull CameraStateFlutterApi api) {
100+
this.cameraStateFlutterApi = api;
101+
}
102+
}

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ public CameraSelector.Builder createCameraSelectorBuilder() {
3232
return new CameraSelector.Builder();
3333
}
3434

35+
/** Creates an instance of {@link CameraPermissionsManager}. */
3536
public CameraPermissionsManager createCameraPermissionsManager() {
3637
return new CameraPermissionsManager();
3738
}
3839

40+
/** Creates an instance of the {@link DeviceOrientationManager}. */
3941
public DeviceOrientationManager createDeviceOrientationManager(
4042
@NonNull Activity activity,
4143
@NonNull Boolean isFrontFacing,
@@ -44,16 +46,18 @@ public DeviceOrientationManager createDeviceOrientationManager(
4446
return new DeviceOrientationManager(activity, isFrontFacing, sensorOrientation, callback);
4547
}
4648

49+
/** Creates a builder for an instance of the {@link Preview} use case. */
4750
public Preview.Builder createPreviewBuilder() {
4851
return new Preview.Builder();
4952
}
5053

54+
/** Creates a {@link Surface} instance from the specified {@link SurfaceTexture}. */
5155
public Surface createSurface(@NonNull SurfaceTexture surfaceTexture) {
5256
return new Surface(surfaceTexture);
5357
}
5458

5559
/**
56-
* Creates an instance of the {@code SystemServicesFlutterApiImpl}.
60+
* Creates an instance of the {@link SystemServicesFlutterApiImpl}.
5761
*
5862
* <p>Included in this class to utilize the callback methods it provides, e.g. {@code
5963
* onCameraError(String)}.
@@ -69,6 +73,7 @@ public Recorder.Builder createRecorderBuilder() {
6973
return new Recorder.Builder();
7074
}
7175

76+
/** Creates a builder for an instance of the {@link ImageCapture} use case. */
7277
@NonNull
7378
public ImageCapture.Builder createImageCaptureBuilder() {
7479
return new ImageCapture.Builder();

0 commit comments

Comments
 (0)