blob: 3307592083818a4d16231877c2afd656e2f8b9ea [file] [log] [blame]
// 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.imagepicker;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.imagepicker.Messages.FlutterError;
import io.flutter.plugins.imagepicker.Messages.ImagePickerApi;
import io.flutter.plugins.imagepicker.Messages.Result;
import io.flutter.plugins.imagepicker.Messages.SourceSpecification;
import java.io.File;
import java.util.List;
@SuppressWarnings("deprecation")
public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePickerApi {
private class LifeCycleObserver
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
private final Activity thisActivity;
LifeCycleObserver(Activity activity) {
this.thisActivity = activity;
}
@Override
public void onCreate(@NonNull LifecycleOwner owner) {}
@Override
public void onStart(@NonNull LifecycleOwner owner) {}
@Override
public void onResume(@NonNull LifecycleOwner owner) {}
@Override
public void onPause(@NonNull LifecycleOwner owner) {}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
onActivityStopped(thisActivity);
}
@Override
public void onDestroy(@NonNull LifecycleOwner owner) {
onActivityDestroyed(thisActivity);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
@Override
public void onActivityStarted(Activity activity) {}
@Override
public void onActivityResumed(Activity activity) {}
@Override
public void onActivityPaused(Activity activity) {}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override
public void onActivityDestroyed(Activity activity) {
if (thisActivity == activity && activity.getApplicationContext() != null) {
((Application) activity.getApplicationContext())
.unregisterActivityLifecycleCallbacks(
this); // Use getApplicationContext() to avoid casting failures
}
}
@Override
public void onActivityStopped(Activity activity) {
if (thisActivity == activity) {
activityState.getDelegate().saveStateBeforeResult();
}
}
}
/**
* Move all activity-lifetime-bound states into this helper object, so that {@code setup} and
* {@code tearDown} would just become constructor and finalize calls of the helper object.
*/
private class ActivityState {
private Application application;
private Activity activity;
private ImagePickerDelegate delegate;
private LifeCycleObserver observer;
private ActivityPluginBinding activityBinding;
private BinaryMessenger messenger;
// This is null when not using v2 embedding;
private Lifecycle lifecycle;
// Default constructor
ActivityState(
final Application application,
final Activity activity,
final BinaryMessenger messenger,
final ImagePickerApi handler,
final PluginRegistry.Registrar registrar,
final ActivityPluginBinding activityBinding) {
this.application = application;
this.activity = activity;
this.activityBinding = activityBinding;
this.messenger = messenger;
delegate = constructDelegate(activity);
ImagePickerApi.setup(messenger, handler);
observer = new LifeCycleObserver(activity);
if (registrar != null) {
// V1 embedding setup for activity listeners.
application.registerActivityLifecycleCallbacks(observer);
registrar.addActivityResultListener(delegate);
registrar.addRequestPermissionsResultListener(delegate);
} else {
// V2 embedding setup for activity listeners.
activityBinding.addActivityResultListener(delegate);
activityBinding.addRequestPermissionsResultListener(delegate);
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding);
lifecycle.addObserver(observer);
}
}
// Only invoked by {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing.
ActivityState(final ImagePickerDelegate delegate, final Activity activity) {
this.activity = activity;
this.delegate = delegate;
}
void release() {
if (activityBinding != null) {
activityBinding.removeActivityResultListener(delegate);
activityBinding.removeRequestPermissionsResultListener(delegate);
activityBinding = null;
}
if (lifecycle != null) {
lifecycle.removeObserver(observer);
lifecycle = null;
}
ImagePickerApi.setup(messenger, null);
if (application != null) {
application.unregisterActivityLifecycleCallbacks(observer);
application = null;
}
activity = null;
observer = null;
delegate = null;
}
Activity getActivity() {
return activity;
}
ImagePickerDelegate getDelegate() {
return delegate;
}
}
private FlutterPluginBinding pluginBinding;
ActivityState activityState;
@SuppressWarnings("deprecation")
public static void registerWith(
@NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
if (registrar.activity() == null) {
// If a background flutter view tries to register the plugin, there will be no activity from the registrar,
// we stop the registering process immediately because the ImagePicker requires an activity.
return;
}
Activity activity = registrar.activity();
Application application = (Application) (registrar.context().getApplicationContext());
ImagePickerPlugin plugin = new ImagePickerPlugin();
plugin.setup(registrar.messenger(), application, activity, registrar, null);
}
/**
* Default constructor for the plugin.
*
* <p>Use this constructor for production code.
*/
// See also: * {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing.
public ImagePickerPlugin() {}
@VisibleForTesting
ImagePickerPlugin(final ImagePickerDelegate delegate, final Activity activity) {
activityState = new ActivityState(delegate, activity);
}
@VisibleForTesting
final ActivityState getActivityState() {
return activityState;
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
pluginBinding = binding;
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
pluginBinding = null;
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
setup(
pluginBinding.getBinaryMessenger(),
(Application) pluginBinding.getApplicationContext(),
binding.getActivity(),
null,
binding);
}
@Override
public void onDetachedFromActivity() {
tearDown();
}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}
private void setup(
final BinaryMessenger messenger,
final Application application,
final Activity activity,
final PluginRegistry.Registrar registrar,
final ActivityPluginBinding activityBinding) {
activityState =
new ActivityState(application, activity, messenger, this, registrar, activityBinding);
}
private void tearDown() {
if (activityState != null) {
activityState.release();
activityState = null;
}
}
@VisibleForTesting
final ImagePickerDelegate constructDelegate(final Activity setupActivity) {
final ImagePickerCache cache = new ImagePickerCache(setupActivity);
final File externalFilesDirectory = setupActivity.getCacheDir();
final ExifDataCopier exifDataCopier = new ExifDataCopier();
final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier);
return new ImagePickerDelegate(setupActivity, externalFilesDirectory, imageResizer, cache);
}
private @Nullable ImagePickerDelegate getImagePickerDelegate() {
if (activityState == null || activityState.getActivity() == null) {
return null;
}
return activityState.getDelegate();
}
private void setCameraDevice(
@NonNull ImagePickerDelegate delegate, @NonNull SourceSpecification source) {
Messages.SourceCamera camera = source.getCamera();
if (camera != null) {
ImagePickerDelegate.CameraDevice device;
switch (camera) {
case FRONT:
device = ImagePickerDelegate.CameraDevice.FRONT;
break;
case REAR:
default:
device = ImagePickerDelegate.CameraDevice.REAR;
break;
}
delegate.setCameraDevice(device);
}
}
@Override
public void pickImages(
@NonNull SourceSpecification source,
@NonNull Messages.ImageSelectionOptions options,
@NonNull Boolean allowMultiple,
@NonNull Boolean usePhotoPicker,
@NonNull Result<List<String>> result) {
ImagePickerDelegate delegate = getImagePickerDelegate();
if (delegate == null) {
result.error(
new FlutterError(
"no_activity", "image_picker plugin requires a foreground activity.", null));
return;
}
setCameraDevice(delegate, source);
if (allowMultiple) {
delegate.chooseMultiImageFromGallery(options, usePhotoPicker, result);
} else {
switch (source.getType()) {
case GALLERY:
delegate.chooseImageFromGallery(options, usePhotoPicker, result);
break;
case CAMERA:
delegate.takeImageWithCamera(options, result);
break;
}
}
}
@Override
public void pickVideos(
@NonNull SourceSpecification source,
@NonNull Messages.VideoSelectionOptions options,
@NonNull Boolean allowMultiple,
@NonNull Boolean usePhotoPicker,
@NonNull Result<List<String>> result) {
ImagePickerDelegate delegate = getImagePickerDelegate();
if (delegate == null) {
result.error(
new FlutterError(
"no_activity", "image_picker plugin requires a foreground activity.", null));
return;
}
setCameraDevice(delegate, source);
if (allowMultiple) {
result.error(new RuntimeException("Multi-video selection is not implemented"));
} else {
switch (source.getType()) {
case GALLERY:
delegate.chooseVideoFromGallery(options, usePhotoPicker, result);
break;
case CAMERA:
delegate.takeVideoWithCamera(options, result);
break;
}
}
}
@Nullable
@Override
public Messages.CacheRetrievalResult retrieveLostResults() {
ImagePickerDelegate delegate = getImagePickerDelegate();
if (delegate == null) {
throw new FlutterError(
"no_activity", "image_picker plugin requires a foreground activity.", null);
}
return delegate.retrieveLostImage();
}
}