-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[camerax] Implement onCameraClosing #3419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 61 commits
6125eca
5be1371
b2fce3d
ed62a9a
2e3027b
fbd3fe5
dfa477e
a651b92
884fd6c
d5f8b91
060b7e5
ff50c70
fe6f1fc
e2643ae
b7194f7
dadc230
d15e1d4
6dc2b77
f826b62
0f7f29a
1e9739b
9ba20d0
5bf6b17
5e58b2c
4c8c94d
6a0de08
d3ed3e7
d1c684f
c04d8c1
b2322d2
3545aec
e1f5bdb
2d3409b
1f40f47
6c9b04e
6c6cbb4
a0ea3c5
06c1545
681a117
1143a03
d431511
ab3c085
631f972
70c817a
426b438
2bc4877
0fcf7c6
6c49bd9
b9daeb1
3345878
9f6646f
265eb1e
bfc4b5e
4f3c342
e3c7a5b
f341f7f
0d4b021
ca3bd9e
540ee98
debcd3a
a79ee62
438f592
c44fc06
83aea10
5f435e8
581af94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| package io.flutter.plugins.camerax; | ||
|
|
||
| import androidx.annotation.NonNull; | ||
| import androidx.camera.core.Camera; | ||
| import androidx.camera.core.CameraInfo; | ||
| import io.flutter.plugin.common.BinaryMessenger; | ||
| import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraHostApi; | ||
| import java.util.Objects; | ||
|
|
||
| public class CameraHostApiImpl implements CameraHostApi { | ||
| private final BinaryMessenger binaryMessenger; | ||
| private final InstanceManager instanceManager; | ||
|
|
||
| public CameraHostApiImpl(BinaryMessenger binaryMessenger, InstanceManager instanceManager) { | ||
|
camsim99 marked this conversation as resolved.
Outdated
|
||
| this.binaryMessenger = binaryMessenger; | ||
| this.instanceManager = instanceManager; | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the {@link CameraInfo} instance that contains information about the {@link Camera} | ||
| * instance with the specified identifier. | ||
| */ | ||
| @Override | ||
| public Long getCameraInfo(@NonNull Long identifier) { | ||
| Camera camera = (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier)); | ||
| CameraInfo cameraInfo = camera.getCameraInfo(); | ||
|
|
||
| if (!instanceManager.containsInstance(cameraInfo)) { | ||
| CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl = | ||
| new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); | ||
| cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {}); | ||
|
camsim99 marked this conversation as resolved.
|
||
| } | ||
| return instanceManager.getIdentifierForStrongReference(cameraInfo); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| package io.flutter.plugins.camerax; | ||
|
|
||
| import androidx.annotation.NonNull; | ||
| import androidx.annotation.VisibleForTesting; | ||
| import androidx.camera.core.CameraState; | ||
| import io.flutter.plugin.common.BinaryMessenger; | ||
| import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateErrorFlutterApi; | ||
|
|
||
| /** | ||
| * Flutter API implementation for {@link CameraStateError}. | ||
| * | ||
| * <p>This class may handle adding native instances that are attached to a Dart instance or passing | ||
| * arguments of callbacks methods to a Dart instance. | ||
| */ | ||
| public class CameraStateErrorFlutterApiWrapper { | ||
| private final BinaryMessenger binaryMessenger; | ||
| private final InstanceManager instanceManager; | ||
| private CameraStateErrorFlutterApi cameraStateErrorFlutterApi; | ||
|
|
||
| /** | ||
| * Constructs a {@link CameraStateErrorFlutterApiWrapper}. | ||
| * | ||
| * @param binaryMessenger used to communicate with Dart over asynchronous messages | ||
| * @param instanceManager maintains instances stored to communicate with attached Dart objects | ||
| */ | ||
| public CameraStateErrorFlutterApiWrapper( | ||
| @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { | ||
| this.binaryMessenger = binaryMessenger; | ||
| this.instanceManager = instanceManager; | ||
| cameraStateErrorFlutterApi = new CameraStateErrorFlutterApi(binaryMessenger); | ||
| } | ||
|
|
||
| /** | ||
| * Stores the {@link CameraStateError} instance and notifies Dart to create and store a new {@link | ||
| * CameraStateError} instance that is attached to this one. If {@code instance} has already been | ||
| * added, this method does nothing. | ||
| */ | ||
|
camsim99 marked this conversation as resolved.
|
||
| public void create( | ||
| @NonNull CameraState.StateError instance, | ||
| @NonNull Long code, | ||
| @NonNull String description, | ||
| @NonNull CameraStateErrorFlutterApi.Reply<Void> callback) { | ||
|
reidbaker marked this conversation as resolved.
|
||
| if (!instanceManager.containsInstance(instance)) { | ||
| cameraStateErrorFlutterApi.create( | ||
| instanceManager.addHostCreatedInstance(instance), code, description, callback); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Sets the Flutter API used to send messages to Dart. | ||
| * | ||
| * <p>This is only visible for testing. | ||
| */ | ||
| @VisibleForTesting | ||
| void setApi(@NonNull CameraStateErrorFlutterApi api) { | ||
| this.cameraStateErrorFlutterApi = api; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| package io.flutter.plugins.camerax; | ||
|
|
||
| import androidx.annotation.NonNull; | ||
| import androidx.annotation.Nullable; | ||
| import androidx.annotation.VisibleForTesting; | ||
| import androidx.camera.core.CameraState; | ||
| import io.flutter.plugin.common.BinaryMessenger; | ||
| import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateFlutterApi; | ||
| import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateType; | ||
| import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraStateTypeData; | ||
|
|
||
| /** | ||
| * Flutter API implementation for {@link CameraState}. | ||
| * | ||
| * <p>This class may handle adding native instances that are attached to a Dart instance or passing | ||
| * arguments of callbacks methods to a Dart instance. | ||
| */ | ||
| public class CameraStateFlutterApiWrapper { | ||
| private final BinaryMessenger binaryMessenger; | ||
| private final InstanceManager instanceManager; | ||
| private CameraStateFlutterApi cameraStateFlutterApi; | ||
|
|
||
| /** | ||
| * Constructs a {@link CameraStateFlutterApiWrapper}. | ||
| * | ||
| * @param binaryMessenger used to communicate with Dart over asynchronous messages | ||
| * @param instanceManager maintains instances stored to communicate with attached Dart objects | ||
| */ | ||
| public CameraStateFlutterApiWrapper( | ||
| @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { | ||
| this.binaryMessenger = binaryMessenger; | ||
| this.instanceManager = instanceManager; | ||
| cameraStateFlutterApi = new CameraStateFlutterApi(binaryMessenger); | ||
| } | ||
|
|
||
| /** | ||
| * Stores the {@link CameraState} instance and notifies Dart to create and store a new {@link | ||
| * CameraState} instance that is attached to this one. If {@code instance} has already been added, | ||
| * this method does nothing. | ||
| */ | ||
| public void create( | ||
|
camsim99 marked this conversation as resolved.
|
||
| @NonNull CameraState instance, | ||
| @NonNull CameraState.Type type, | ||
| @Nullable CameraState.StateError error, | ||
| @NonNull CameraStateFlutterApi.Reply<Void> callback) { | ||
|
|
||
| // Convert CameraX CameraState.Type to CameraStateType that the Dart side understands. | ||
| CameraStateType cameraStateType = CameraStateType.CLOSED; | ||
|
camsim99 marked this conversation as resolved.
Outdated
camsim99 marked this conversation as resolved.
Outdated
|
||
| switch (type) { | ||
| case CLOSED: | ||
| cameraStateType = CameraStateType.CLOSED; | ||
| break; | ||
| case CLOSING: | ||
| cameraStateType = CameraStateType.CLOSING; | ||
| break; | ||
| case OPEN: | ||
| cameraStateType = CameraStateType.OPEN; | ||
| break; | ||
| case OPENING: | ||
| cameraStateType = CameraStateType.OPENING; | ||
| break; | ||
| case PENDING_OPEN: | ||
| cameraStateType = CameraStateType.PENDING_OPEN; | ||
| break; | ||
| } | ||
|
|
||
| if (error != null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider moving the error checking higher in the function since we don't need the conversion code to run if there is an error.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error is separate from the type, so we'll still need it. I did, however, move the instance manager check higher. |
||
| // We need to create a CameraStateError if there is a problem with the current camera | ||
| // state to send to the Dart side. | ||
| new CameraStateErrorFlutterApiWrapper(binaryMessenger, instanceManager) | ||
| .create( | ||
| error, | ||
| Long.valueOf(error.getCode()), | ||
| getCameraStateErrorDescription(error), | ||
| reply -> {}); | ||
| } | ||
|
|
||
| if (!instanceManager.containsInstance(instance)) { | ||
| cameraStateFlutterApi.create( | ||
| instanceManager.addHostCreatedInstance(instance), | ||
| new CameraStateTypeData.Builder().setValue(cameraStateType).build(), | ||
| instanceManager.getIdentifierForStrongReference(error), | ||
| callback); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns an error message corresponding to the specified {@link CameraState.StateError}. | ||
| * | ||
| * <p>See https://developer.android.com/reference/androidx/camera/core/CameraState#constants_1 for | ||
| * more information on the different {@link CameraState.StateError} types. | ||
| */ | ||
| private String getCameraStateErrorDescription(@NonNull CameraState.StateError cameraStateError) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you include who this error string is for? Ex for developers, can it be shown to users, can it be used for error handling etc? My guess is it is a Developer visible string and should not be used in error handling.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved this to Dart and specified this in the documentation. |
||
| final int cameraStateErrorCode = cameraStateError.getCode(); | ||
| final String cameraStateErrorDescription = cameraStateErrorCode + ": "; | ||
| switch (cameraStateErrorCode) { | ||
| case CameraState.ERROR_CAMERA_IN_USE: | ||
| return cameraStateErrorDescription | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that all these values are cameraStateErrorCode + SOME_STRING, I suggest making this easier to read by setting a temporary string (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. Changed this on the Dart side, where I moved it. I decided to move it because this is not directly provided by CameraX and so I don't think we should be wrapping it like it is. |
||
| + "The camera was already in use, possibly by a higher-priority camera client."; | ||
| case CameraState.ERROR_MAX_CAMERAS_IN_USE: | ||
| return cameraStateErrorDescription | ||
| + "The limit number of open cameras has been reached, and more cameras cannot be opened until other instances are closed."; | ||
| case CameraState.ERROR_OTHER_RECOVERABLE_ERROR: | ||
| return cameraStateErrorDescription | ||
| + "The camera device has encountered a recoverable error. CameraX will attempt to recover from the error."; | ||
| case CameraState.ERROR_STREAM_CONFIG: | ||
| return cameraStateErrorDescription + "Configuring the camera has failed."; | ||
| case CameraState.ERROR_CAMERA_DISABLED: | ||
| return cameraStateErrorDescription | ||
| + "The camera device could not be opened due to a device policy. Thia may be caused by a client from a background process attempting to open the camera."; | ||
| case CameraState.ERROR_CAMERA_FATAL_ERROR: | ||
| return cameraStateErrorDescription | ||
| + "The camera was closed due to a fatal error. This may require the Android device be shut down and restarted to restore camera function or may indicate a persistent camera hardware problem."; | ||
| case CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED: | ||
| return cameraStateErrorDescription | ||
| + "The camera could not be opened because 'Do Not Disturb' mode is enabled. Please disable this mode, and try opening the camera again."; | ||
| default: | ||
| return cameraStateErrorDescription + "There was an unspecified issue with the current camera state."; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Sets the Flutter API used to send messages to Dart. | ||
| * | ||
| * <p>This is only visible for testing. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this is redundant with the annotation. Consider removing or adding context for why test methods would want to set it or maybe why real apps would not.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is generated, so maybe this can be feedback for the generator? @bparrishMines |
||
| */ | ||
| @VisibleForTesting | ||
| void setApi(@NonNull CameraStateFlutterApi api) { | ||
| this.cameraStateFlutterApi = api; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems weird to me that this method takes in an
ActivityPluginBindingbut all the values that are updated referencepluginBindingandapplicationContextis used frompluginBindinginstead of the activity context that is available onactivityPluginBindingThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems weird to me too. Definitely mistake on my end; fixing.