Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
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/image_picker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.6.3

* Support Android V2 embedding.
* Migrate to using the new e2e test binding.

## 0.6.2+3
* Remove the deprecated `author:` field from pubspec.yaml
* Migrate the plugin to the pubspec platforms manifest.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ public void onScanCompleted(String path, Uri uri) {
this.cache = cache;
}

// Save the state of the image picker so it can be retrieved with `retrieveLostImage`.
void saveStateBeforeResult() {
if (methodCall == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,86 @@
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
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.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import java.io.File;

public class ImagePickerPlugin implements MethodChannel.MethodCallHandler {
@SuppressWarnings("deprecation")
public class ImagePickerPlugin
implements MethodChannel.MethodCallHandler, FlutterPlugin, ActivityAware {

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) {}
Comment on lines +40 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why override these empty methods?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂️ because it's implements, not extends. My mistake. This class does need all these empty methods defined still, sorry.


@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) {
delegate.saveStateBeforeResult();
}
}
}

static final String METHOD_CALL_IMAGE = "pickImage";
static final String METHOD_CALL_VIDEO = "pickVideo";
Expand All @@ -27,82 +100,130 @@ public class ImagePickerPlugin implements MethodChannel.MethodCallHandler {
private static final int SOURCE_CAMERA = 0;
private static final int SOURCE_GALLERY = 1;

private final PluginRegistry.Registrar registrar;
private MethodChannel channel;
private ImagePickerDelegate delegate;
private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks;
private FlutterPluginBinding pluginBinding;
private ActivityPluginBinding activityBinding;
private Application application;
private Activity activity;
// This is null when not using v2 embedding;
private Lifecycle lifecycle;
private LifeCycleObserver observer;

public static void registerWith(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;
}
final ImagePickerCache cache = new ImagePickerCache(registrar.activity());
Activity activity = registrar.activity();
Application application = null;
if (registrar.context() != null) {
application = (Application) (registrar.context().getApplicationContext());
}
ImagePickerPlugin plugin = new ImagePickerPlugin();
plugin.setup(registrar.messenger(), application, activity, registrar, null);
}

final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL);
/**
* Default constructor for the plugin.
*
* <p>Use this constructor for production code.
*/
// See also: * {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing.
public ImagePickerPlugin() {}

final File externalFilesDirectory =
registrar.activity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
final ExifDataCopier exifDataCopier = new ExifDataCopier();
final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier);
final ImagePickerDelegate delegate =
new ImagePickerDelegate(registrar.activity(), externalFilesDirectory, imageResizer, cache);
@VisibleForTesting
ImagePickerPlugin(final ImagePickerDelegate delegate, final Activity activity) {
this.delegate = delegate;
this.activity = activity;
}

registrar.addActivityResultListener(delegate);
registrar.addRequestPermissionsResultListener(delegate);
final ImagePickerPlugin instance = new ImagePickerPlugin(registrar, delegate);
channel.setMethodCallHandler(instance);
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
pluginBinding = binding;
}

@VisibleForTesting
ImagePickerPlugin(final PluginRegistry.Registrar registrar, final ImagePickerDelegate delegate) {
this.registrar = registrar;
this.delegate = delegate;
this.activityLifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
pluginBinding = null;
}

@Override
public void onActivityStarted(Activity activity) {}
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
activityBinding = binding;
setup(
pluginBinding.getBinaryMessenger(),
(Application) pluginBinding.getApplicationContext(),
activityBinding.getActivity(),
null,
activityBinding);
}

@Override
public void onActivityResumed(Activity activity) {}
@Override
public void onDetachedFromActivity() {
tearDown();
}

@Override
public void onActivityPaused(Activity activity) {}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
if (activity == registrar.activity()) {
delegate.saveStateBeforeResult();
}
}

@Override
public void onActivityDestroyed(Activity activity) {
if (activity == registrar.activity()
&& registrar.activity().getApplicationContext() != null) {
((Application) registrar.activity().getApplicationContext())
.unregisterActivityLifecycleCallbacks(
this); // Use getApplicationContext() to avoid casting failures
}
}

@Override
public void onActivityStopped(Activity activity) {}
};

if (this.registrar != null
&& this.registrar.context() != null
&& this.registrar.context().getApplicationContext() != null) {
((Application) this.registrar.context().getApplicationContext())
.registerActivityLifecycleCallbacks(
this
.activityLifecycleCallbacks); // Use getApplicationContext() to avoid casting failures.
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}

private void setup(
final BinaryMessenger messenger,
final Application application,
final Activity activity,
final PluginRegistry.Registrar registrar,
final ActivityPluginBinding activityBinding) {
this.activity = activity;
this.application = application;
this.delegate = constructDelegate(activity);
channel = new MethodChannel(messenger, CHANNEL);
channel.setMethodCallHandler(this);
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);
}
}

private void tearDown() {
activityBinding.removeActivityResultListener(delegate);
activityBinding.removeRequestPermissionsResultListener(delegate);
activityBinding = null;
lifecycle.removeObserver(observer);
lifecycle = null;
delegate = null;
channel.setMethodCallHandler(null);
channel = null;
application.unregisterActivityLifecycleCallbacks(observer);
application = null;
}

private final ImagePickerDelegate constructDelegate(final Activity setupActivity) {
final ImagePickerCache cache = new ImagePickerCache(setupActivity);

final File externalFilesDirectory =
setupActivity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
final ExifDataCopier exifDataCopier = new ExifDataCopier();
final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier);
return new ImagePickerDelegate(setupActivity, externalFilesDirectory, imageResizer, cache);
}

// MethodChannel.Result wrapper that responds on the platform thread.
private static class MethodResultWrapper implements MethodChannel.Result {
private MethodChannel.Result methodResult;
Expand Down Expand Up @@ -150,7 +271,7 @@ public void run() {

@Override
public void onMethodCall(MethodCall call, MethodChannel.Result rawResult) {
if (registrar.activity() == null) {
if (activity == null) {
rawResult.error("no_activity", "image_picker plugin requires a foreground activity.", null);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
<uses-permission android:name="android.permission.INTERNET"/>

<application android:name="io.flutter.app.FlutterApplication" android:label="Image Picker Example" android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity"
android:launchMode="singleTop"
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
Expand All @@ -15,5 +14,12 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".EmbeddingV1Activity"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Expand All @@ -8,8 +8,7 @@
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

public class EmbeddingV1Activity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2019 The Chromium 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.imagepickerexample;

import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.e2e.FlutterRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;

@RunWith(FlutterRunner.class)
public class EmbeddingV1ActivityTest {
@Rule
public ActivityTestRule<EmbeddingV1Activity> rule =
new ActivityTestRule<>(EmbeddingV1Activity.class);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.flutter.plugins.imagepickerexample;

import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.e2e.FlutterRunner;
import io.flutter.embedding.android.FlutterActivity;
import org.junit.Rule;
import org.junit.runner.RunWith;

@RunWith(FlutterRunner.class)
public class FlutterActivityTest {
@Rule
public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
}
Loading