Skip to content
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

[camerax] Update Activity references when ActivityAware lifecycle methods called #5784

Merged
merged 8 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.5.0+27

* Removes or updates any references to an `ActivityPluginBinding` when the plugin is detached
or attached/re-attached, respectively, to an `Activity.`

## 0.5.0+26

* Fixes new lint warnings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
public final class CameraAndroidCameraxPlugin implements FlutterPlugin, ActivityAware {
private InstanceManager instanceManager;
private FlutterPluginBinding pluginBinding;
private PendingRecordingHostApiImpl pendingRecordingHostApiImpl;
private RecorderHostApiImpl recorderHostApiImpl;
private VideoCaptureHostApiImpl videoCaptureHostApiImpl;
private ImageAnalysisHostApiImpl imageAnalysisHostApiImpl;
private ImageCaptureHostApiImpl imageCaptureHostApiImpl;
private CameraControlHostApiImpl cameraControlHostApiImpl;
public @Nullable SystemServicesHostApiImpl systemServicesHostApiImpl;
@VisibleForTesting @Nullable public PendingRecordingHostApiImpl pendingRecordingHostApiImpl;
@VisibleForTesting @Nullable public RecorderHostApiImpl recorderHostApiImpl;
@VisibleForTesting @Nullable public VideoCaptureHostApiImpl videoCaptureHostApiImpl;
@VisibleForTesting @Nullable public ImageAnalysisHostApiImpl imageAnalysisHostApiImpl;
@VisibleForTesting @Nullable public ImageCaptureHostApiImpl imageCaptureHostApiImpl;
@VisibleForTesting @Nullable public CameraControlHostApiImpl cameraControlHostApiImpl;
@VisibleForTesting @Nullable public SystemServicesHostApiImpl systemServicesHostApiImpl;

@VisibleForTesting
public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl;

@VisibleForTesting
Expand Down Expand Up @@ -136,42 +138,54 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
Activity activity = activityPluginBinding.getActivity();

// Set up Host API implementations based on the context that `activity` provides.
setUp(pluginBinding.getBinaryMessenger(), activity, pluginBinding.getTextureRegistry());

if (activity instanceof LifecycleOwner) {
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
liveDataHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
} else {
ProxyLifecycleProvider proxyLifecycleProvider = new ProxyLifecycleProvider(activity);
processCameraProviderHostApiImpl.setLifecycleOwner(proxyLifecycleProvider);
liveDataHostApiImpl.setLifecycleOwner(proxyLifecycleProvider);
}
// Set any needed references to `activity` itself.
updateLifecycleOwner(activity);
updateActivity(activity);

systemServicesHostApiImpl.setActivity(activity);
// Set permissions registry reference.
systemServicesHostApiImpl.setPermissionsRegistry(
activityPluginBinding::addRequestPermissionsResultListener);
deviceOrientationManagerHostApiImpl.setActivity(activity);
}

@Override
public void onDetachedFromActivityForConfigChanges() {
// Clear any references to previously attached `ActivityPluginBinding`/`Activity`.
updateContext(pluginBinding.getApplicationContext());
updateLifecycleOwner(null);
updateActivity(null);
systemServicesHostApiImpl.setPermissionsRegistry(null);
}

@Override
public void onReattachedToActivityForConfigChanges(
@NonNull ActivityPluginBinding activityPluginBinding) {
updateContext(activityPluginBinding.getActivity());
Activity activity = activityPluginBinding.getActivity();

// Set any needed references to `activity` itself or its context.
updateContext(activity);
updateLifecycleOwner(activity);
updateActivity(activity);

// Set permissions registry reference.
systemServicesHostApiImpl.setPermissionsRegistry(
activityPluginBinding::addRequestPermissionsResultListener);
}

@Override
public void onDetachedFromActivity() {
// Clear any references to previously attached `ActivityPluginBinding`/`Activity`.
updateContext(pluginBinding.getApplicationContext());
updateLifecycleOwner(null);
updateActivity(null);
systemServicesHostApiImpl.setPermissionsRegistry(null);
}

/**
* Updates context that is used to fetch the corresponding instance of a {@code
* ProcessCameraProvider}.
* ProcessCameraProvider} to each of the relevant camera controls.
*/
public void updateContext(@NonNull Context context) {
if (processCameraProviderHostApiImpl != null) {
Expand All @@ -196,4 +210,32 @@ public void updateContext(@NonNull Context context) {
cameraControlHostApiImpl.setContext(context);
}
}

/** Sets {@code LifecycleOwner} that is used to control the lifecycle of the camera by CameraX. */
public void updateLifecycleOwner(@Nullable Activity activity) {
if (activity == null) {
processCameraProviderHostApiImpl.setLifecycleOwner(null);
liveDataHostApiImpl.setLifecycleOwner(null);
} else if (activity instanceof LifecycleOwner) {
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
liveDataHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
} else {
ProxyLifecycleProvider proxyLifecycleProvider = new ProxyLifecycleProvider(activity);
processCameraProviderHostApiImpl.setLifecycleOwner(proxyLifecycleProvider);
liveDataHostApiImpl.setLifecycleOwner(proxyLifecycleProvider);
}
}

/**
* Updates {@code Activity} that is used for requesting camera permissions and tracking the
* orientation of the device.
*/
public void updateActivity(@Nullable Activity activity) {
if (systemServicesHostApiImpl != null) {
systemServicesHostApiImpl.setActivity(activity);
}
if (deviceOrientationManagerHostApiImpl != null) {
deviceOrientationManagerHostApiImpl.setActivity(activity);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public void enableTorch(
@NonNull CameraControl cameraControl,
@NonNull Boolean torch,
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
if (context == null) {
throw new IllegalStateException("Context must be set to enable the torch.");
}

ListenableFuture<Void> enableTorchFuture = cameraControl.enableTorch(torch);

Futures.addCallback(
Expand All @@ -58,6 +62,10 @@ public void setZoomRatio(
@NonNull CameraControl cameraControl,
@NonNull Double ratio,
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
if (context == null) {
throw new IllegalStateException("Context must be set to set zoom ratio.");
}

float ratioAsFloat = ratio.floatValue();
ListenableFuture<Void> setZoomRatioFuture = cameraControl.setZoomRatio(ratioAsFloat);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public void setActivity(@NonNull Activity activity) {
@Override
public void startListeningForDeviceOrientationChange(
@NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation) {
if (activity == null) {
throw new IllegalStateException(
"Activity must be set to start listening for device orientation changes.");
}

deviceOrientationManager =
cameraXProxy.createDeviceOrientationManager(
activity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi {

private InstanceManager instanceManager;
private BinaryMessenger binaryMessenger;
private Context context;
@Nullable private Context context;

@VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy();

Expand Down Expand Up @@ -65,6 +65,10 @@ public void create(
*/
@Override
public void setAnalyzer(@NonNull Long identifier, @NonNull Long analyzerIdentifier) {
if (context == null) {
throw new IllegalStateException("Context must be set to set an Analyzer.");
}

getImageAnalysisInstance(identifier)
.setAnalyzer(
ContextCompat.getMainExecutor(context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class ImageCaptureHostApiImpl implements ImageCaptureHostApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;

private Context context;
@Nullable private Context context;
private SystemServicesFlutterApiImpl systemServicesFlutterApiImpl;

public static final String TEMPORARY_FILE_NAME = "CAP";
Expand Down Expand Up @@ -87,6 +87,10 @@ public void setFlashMode(@NonNull Long identifier, @NonNull Long flashMode) {
@Override
public void takePicture(
@NonNull Long identifier, @NonNull GeneratedCameraXLibrary.Result<String> result) {
if (context == null) {
throw new IllegalStateException("Context must be set to take picture.");
}

ImageCapture imageCapture = getImageCaptureInstance(identifier);
final File outputDir = context.getCacheDir();
File temporaryCaptureFile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
public class LiveDataHostApiImpl implements LiveDataHostApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;
private LifecycleOwner lifecycleOwner;
@Nullable private LifecycleOwner lifecycleOwner;

/**
* Constructs a {@link LiveDataHostApiImpl}.
Expand All @@ -40,7 +40,7 @@ public LiveDataHostApiImpl(
}

/** Sets {@link LifecycleOwner} used to observe the camera state if so requested. */
public void setLifecycleOwner(@NonNull LifecycleOwner lifecycleOwner) {
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
this.lifecycleOwner = lifecycleOwner;
camsim99 marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -51,6 +51,10 @@ public void setLifecycleOwner(@NonNull LifecycleOwner lifecycleOwner) {
@Override
@SuppressWarnings("unchecked")
public void observe(@NonNull Long identifier, @NonNull Long observerIdentifier) {
if (lifecycleOwner == null) {
throw new IllegalStateException("LifecycleOwner must be set to observe a LiveData instance.");
}

getLiveDataInstance(identifier)
.observe(
lifecycleOwner,
Expand All @@ -60,6 +64,10 @@ public void observe(@NonNull Long identifier, @NonNull Long observerIdentifier)
/** Removes all observers of this instance that are tied to the {@link lifecycleOwner}. */
@Override
public void removeObservers(@NonNull Long identifier) {
if (lifecycleOwner == null) {
throw new IllegalStateException("LifecycleOwner must be set to remove LiveData observers.");
}

getLiveDataInstance(identifier).removeObservers(lifecycleOwner);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
public class PendingRecordingHostApiImpl implements PendingRecordingHostApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;
private Context context;
@Nullable private Context context;

@VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy();

Expand Down Expand Up @@ -63,6 +63,10 @@ public Long start(@NonNull Long identifier) {
@Nullable
@VisibleForTesting
public Executor getExecutor() {
if (context == null) {
throw new IllegalStateException("Context must be set to get an executor to start recording.");
}

return ContextCompat.getMainExecutor(context);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
Expand All @@ -24,8 +25,8 @@ public class ProcessCameraProviderHostApiImpl implements ProcessCameraProviderHo
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;

private Context context;
private LifecycleOwner lifecycleOwner;
@Nullable private Context context;
@Nullable private LifecycleOwner lifecycleOwner;

public ProcessCameraProviderHostApiImpl(
@NonNull BinaryMessenger binaryMessenger,
Expand All @@ -36,7 +37,7 @@ public ProcessCameraProviderHostApiImpl(
this.context = context;
}

public void setLifecycleOwner(@NonNull LifecycleOwner lifecycleOwner) {
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
this.lifecycleOwner = lifecycleOwner;
}

Expand All @@ -57,6 +58,10 @@ public void setContext(@NonNull Context context) {
*/
@Override
public void getInstance(@NonNull GeneratedCameraXLibrary.Result<Long> result) {
if (context == null) {
throw new IllegalStateException("Context must be set to get ProcessCameraProvider instance.");
}

ListenableFuture<ProcessCameraProvider> processCameraProviderFuture =
ProcessCameraProvider.getInstance(context);

Expand Down Expand Up @@ -110,6 +115,11 @@ public List<Long> getAvailableCameraInfos(@NonNull Long identifier) {
@NonNull Long identifier,
@NonNull Long cameraSelectorIdentifier,
@NonNull List<Long> useCaseIds) {
if (lifecycleOwner == null) {
throw new IllegalStateException(
"LifecycleOwner must be set to get ProcessCameraProvider instance.");
}

ProcessCameraProvider processCameraProvider =
(ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier));
CameraSelector cameraSelector =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
public class RecorderHostApiImpl implements RecorderHostApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;
private Context context;
@Nullable private Context context;

@NonNull @VisibleForTesting public CameraXProxy cameraXProxy = new CameraXProxy();

Expand All @@ -46,6 +46,10 @@ public void create(
@Nullable Long aspectRatio,
@Nullable Long bitRate,
@Nullable Long qualitySelector) {
if (context == null) {
throw new IllegalStateException("Context must be set to create Recorder instance.");
}

Recorder.Builder recorderBuilder = cameraXProxy.createRecorderBuilder();
if (aspectRatio != null) {
recorderBuilder.setAspectRatio(aspectRatio.intValue());
Expand Down Expand Up @@ -89,6 +93,10 @@ public Long getTargetVideoEncodingBitRate(@NonNull Long identifier) {
@NonNull
@Override
public Long prepareRecording(@NonNull Long identifier, @NonNull String path) {
if (context == null) {
throw new IllegalStateException("Context must be set to prepare recording.");
}

Recorder recorder = getRecorderFromInstanceId(identifier);
File temporaryCaptureFile = openTempFile(path);
FileOutputOptions fileOutputOptions =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry;
Expand All @@ -19,7 +20,7 @@
public class SystemServicesHostApiImpl implements SystemServicesHostApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;
private Context context;
@Nullable private Context context;

@VisibleForTesting public @NonNull CameraXProxy cameraXProxy = new CameraXProxy();
@VisibleForTesting public @NonNull SystemServicesFlutterApiImpl systemServicesFlutterApi;
Expand All @@ -46,7 +47,7 @@ public void setActivity(@NonNull Activity activity) {
this.activity = activity;
}

public void setPermissionsRegistry(@NonNull PermissionsRegistry permissionsRegistry) {
public void setPermissionsRegistry(@Nullable PermissionsRegistry permissionsRegistry) {
this.permissionsRegistry = permissionsRegistry;
}

Expand All @@ -59,6 +60,10 @@ public void setPermissionsRegistry(@NonNull PermissionsRegistry permissionsRegis
@Override
public void requestCameraPermissions(
@NonNull Boolean enableAudio, @NonNull Result<CameraPermissionsErrorData> result) {
if (activity == null) {
throw new IllegalStateException("Activity must be set to request camera permissions.");
}

CameraPermissionsManager cameraPermissionsManager =
cameraXProxy.createCameraPermissionsManager();
cameraPermissionsManager.requestPermissions(
Expand All @@ -84,6 +89,10 @@ public void requestCameraPermissions(
@Override
@NonNull
public String getTempFilePath(@NonNull String prefix, @NonNull String suffix) {
if (context == null) {
throw new IllegalStateException("Context must be set to create a temporary file.");
}

try {
File path = File.createTempFile(prefix, suffix, context.getCacheDir());
return path.toString();
Expand Down
Loading