diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md
index 26090115d466..9b614940823b 100644
--- a/packages/android_intent/CHANGELOG.md
+++ b/packages/android_intent/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 0.3.4
+
+* Migrate the plugin to use the V2 Android engine embedding. This shouldn't
+ affect existing functionality. Plugin authors who use the V2 embedding can now
+ instantiate the plugin and expect that it correctly responds to app lifecycle
+ changes.
+
## 0.3.3+3
* Define clang module for iOS.
@@ -8,11 +15,11 @@
## 0.3.3+1
-* Added "action_application_details_settings" action to open application info settings .
+* Added "action_application_details_settings" action to open application info settings .
## 0.3.3
-* Added "flags" option to call intent.addFlags(int) in native.
+* Added "flags" option to call intent.addFlags(int) in native.
## 0.3.2
diff --git a/packages/android_intent/android/build.gradle b/packages/android_intent/android/build.gradle
index 8b21464f4b19..ceece7ff6a00 100644
--- a/packages/android_intent/android/build.gradle
+++ b/packages/android_intent/android/build.gradle
@@ -44,4 +44,40 @@ android {
lintOptions {
disable 'InvalidPackage'
}
+ testOptions {
+ unitTests.includeAndroidResources = true
+ }
+}
+
+dependencies {
+ compileOnly 'androidx.annotation:annotation:1.0.0'
+ testImplementation 'junit:junit:4.12'
+ testImplementation 'org.mockito:mockito-core:1.10.19'
+ testImplementation 'androidx.test:core:1.0.0'
+ testImplementation 'org.robolectric:robolectric:4.3'
+}
+
+// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348
+afterEvaluate {
+ def containsEmbeddingDependencies = false
+ for (def configuration : configurations.all) {
+ for (def dependency : configuration.dependencies) {
+ if (dependency.group == 'io.flutter' &&
+ dependency.name.startsWith('flutter_embedding') &&
+ dependency.isTransitive())
+ {
+ containsEmbeddingDependencies = true
+ break
+ }
+ }
+ }
+ if (!containsEmbeddingDependencies) {
+ android {
+ dependencies {
+ def lifecycle_version = "2.1.0"
+ api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+ api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
+ }
+ }
+ }
}
diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java
index b6d3c81b1a8c..d2b58814dcf7 100644
--- a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java
+++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java
@@ -1,161 +1,74 @@
-// Copyright 2017 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.androidintent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.util.Log;
-import io.flutter.plugin.common.MethodCall;
-import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
-import io.flutter.plugin.common.MethodChannel.Result;
+import androidx.annotation.NonNull;
+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.plugin.common.PluginRegistry.Registrar;
-import java.util.ArrayList;
-import java.util.Map;
-/** AndroidIntentPlugin */
-@SuppressWarnings("unchecked")
-public class AndroidIntentPlugin implements MethodCallHandler {
- private static final String TAG = AndroidIntentPlugin.class.getCanonicalName();
- private final Registrar mRegistrar;
+/**
+ * Plugin implementation that uses the new {@code io.flutter.embedding} package.
+ *
+ *
Instantiate this in an add to app scenario to gracefully handle activity and context changes.
+ */
+public final class AndroidIntentPlugin implements FlutterPlugin, ActivityAware {
+ private final IntentSender sender;
+ private final MethodCallHandlerImpl impl;
- /** Plugin registration. */
- public static void registerWith(Registrar registrar) {
- final MethodChannel channel =
- new MethodChannel(registrar.messenger(), "plugins.flutter.io/android_intent");
- channel.setMethodCallHandler(new AndroidIntentPlugin(registrar));
+ /**
+ * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment.
+ *
+ *
See {@code io.flutter.plugins.androidintentexample.MainActivity} for an example.
+ */
+ public AndroidIntentPlugin() {
+ sender = new IntentSender(/*activity=*/ null, /*context=*/ null);
+ impl = new MethodCallHandlerImpl(sender);
}
- private AndroidIntentPlugin(Registrar registrar) {
- this.mRegistrar = registrar;
+ /**
+ * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common}
+ * package.
+ *
+ *
Calling this automatically initializes the plugin. However plugins initialized this way
+ * won't react to changes in activity or context, unlike {@link AndroidIntentPlugin}.
+ */
+ public static void registerWith(Registrar registrar) {
+ IntentSender sender = new IntentSender(registrar.activity(), registrar.context());
+ MethodCallHandlerImpl impl = new MethodCallHandlerImpl(sender);
+ impl.startListening(registrar.messenger());
}
- private String convertAction(String action) {
- switch (action) {
- case "action_view":
- return Intent.ACTION_VIEW;
- case "action_voice":
- return Intent.ACTION_VOICE_COMMAND;
- case "settings":
- return Settings.ACTION_SETTINGS;
- case "action_location_source_settings":
- return Settings.ACTION_LOCATION_SOURCE_SETTINGS;
- case "action_application_details_settings":
- return Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
- default:
- return action;
- }
+ @Override
+ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
+ sender.setApplicationContext(binding.getApplicationContext());
+ sender.setActivity(null);
+ impl.startListening(binding.getFlutterEngine().getDartExecutor());
}
- private Bundle convertArguments(Map arguments) {
- Bundle bundle = new Bundle();
- for (String key : arguments.keySet()) {
- Object value = arguments.get(key);
- if (value instanceof Integer) {
- bundle.putInt(key, (Integer) value);
- } else if (value instanceof String) {
- bundle.putString(key, (String) value);
- } else if (value instanceof Boolean) {
- bundle.putBoolean(key, (Boolean) value);
- } else if (value instanceof Double) {
- bundle.putDouble(key, (Double) value);
- } else if (value instanceof Long) {
- bundle.putLong(key, (Long) value);
- } else if (value instanceof byte[]) {
- bundle.putByteArray(key, (byte[]) value);
- } else if (value instanceof int[]) {
- bundle.putIntArray(key, (int[]) value);
- } else if (value instanceof long[]) {
- bundle.putLongArray(key, (long[]) value);
- } else if (value instanceof double[]) {
- bundle.putDoubleArray(key, (double[]) value);
- } else if (isTypedArrayList(value, Integer.class)) {
- bundle.putIntegerArrayList(key, (ArrayList) value);
- } else if (isTypedArrayList(value, String.class)) {
- bundle.putStringArrayList(key, (ArrayList) value);
- } else if (isStringKeyedMap(value)) {
- bundle.putBundle(key, convertArguments((Map) value));
- } else {
- throw new UnsupportedOperationException("Unsupported type " + value);
- }
- }
- return bundle;
+ @Override
+ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
+ sender.setApplicationContext(null);
+ sender.setActivity(null);
+ impl.stopListening();
}
- private boolean isTypedArrayList(Object value, Class> type) {
- if (!(value instanceof ArrayList)) {
- return false;
- }
- ArrayList list = (ArrayList) value;
- for (Object o : list) {
- if (!(o == null || type.isInstance(o))) {
- return false;
- }
- }
- return true;
+ @Override
+ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
+ sender.setActivity(binding.getActivity());
}
- private boolean isStringKeyedMap(Object value) {
- if (!(value instanceof Map)) {
- return false;
- }
- Map map = (Map) value;
- for (Object key : map.keySet()) {
- if (!(key == null || key instanceof String)) {
- return false;
- }
- }
- return true;
+ @Override
+ public void onDetachedFromActivity() {
+ sender.setActivity(null);
}
- private Context getActiveContext() {
- return (mRegistrar.activity() != null) ? mRegistrar.activity() : mRegistrar.context();
+ @Override
+ public void onDetachedFromActivityForConfigChanges() {
+ onDetachedFromActivity();
}
@Override
- public void onMethodCall(MethodCall call, Result result) {
- Context context = getActiveContext();
- String action = convertAction((String) call.argument("action"));
-
- // Build intent
- Intent intent = new Intent(action);
- if (mRegistrar.activity() == null) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- if (call.argument("flag") != null) {
- intent.addFlags((Integer) call.argument("flags"));
- }
- if (call.argument("category") != null) {
- intent.addCategory((String) call.argument("category"));
- }
- if (call.argument("data") != null) {
- intent.setData(Uri.parse((String) call.argument("data")));
- }
- if (call.argument("arguments") != null) {
- intent.putExtras(convertArguments((Map) call.argument("arguments")));
- }
- if (call.argument("package") != null) {
- String packageName = (String) call.argument("package");
- intent.setPackage(packageName);
- if (call.argument("componentName") != null) {
- intent.setComponent(
- new ComponentName(packageName, (String) call.argument("componentName")));
- }
- if (intent.resolveActivity(context.getPackageManager()) == null) {
- Log.i(TAG, "Cannot resolve explicit intent - ignoring package");
- intent.setPackage(null);
- }
- }
-
- Log.i(TAG, "Sending intent " + intent);
- context.startActivity(intent);
-
- result.success(null);
+ public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
+ onAttachedToActivity(binding);
}
}
diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java
new file mode 100644
index 000000000000..13e56ed487e5
--- /dev/null
+++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java
@@ -0,0 +1,110 @@
+package io.flutter.plugins.androidintent;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import androidx.annotation.Nullable;
+
+/** Forms and launches intents. */
+public final class IntentSender {
+ private static final String TAG = "IntentSender";
+
+ private @Nullable Activity activity;
+ private @Nullable Context applicationContext;
+
+ /**
+ * Caches the given {@code activity} and {@code applicationContext} to use for sending intents
+ * later.
+ *
+ * Either may be null initially, but at least {@code applicationContext} should be set before
+ * calling {@link #send}.
+ *
+ *
See also {@link #setActivity}, {@link #setApplicationContext}, and {@link #send}.
+ */
+ public IntentSender(@Nullable Activity activity, @Nullable Context applicationContext) {
+ this.activity = activity;
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * Creates and launches an intent with the given params using the cached {@link Activity} and
+ * {@link Context}.
+ *
+ *
This will fail to create and send the intent if {@code applicationContext} hasn't been set
+ * at the time of calling.
+ *
+ *
This uses {@code activity} to start the intent whenever it's not null. Otherwise it falls
+ * back to {@code applicationContext} and adds {@link Intent#FLAG_ACTIVITY_NEW_TASK} to the intent
+ * before launching it.
+ *
+ * @param action the Intent action, such as {@code ACTION_VIEW}.
+ * @param flags forwarded to {@link Intent#addFlags(int)} if non-null.
+ * @param category forwarded to {@link Intent#addCategory(String)} if non-null.
+ * @param data forwarded to {@link Intent#setData(Uri)} if non-null.
+ * @param arguments forwarded to {@link Intent#putExtras(Bundle)} if non-null.
+ * @param packageName forwarded to {@link Intent#setPackage(String)} if non-null. This is forced
+ * to null if it can't be resolved.
+ * @param componentName forwarded to {@link Intent#setComponent(ComponentName)} if non-null.
+ */
+ void send(
+ String action,
+ @Nullable Integer flags,
+ @Nullable String category,
+ @Nullable Uri data,
+ @Nullable Bundle arguments,
+ @Nullable String packageName,
+ @Nullable ComponentName componentName) {
+ if (applicationContext == null) {
+ Log.wtf(TAG, "Trying to send an intent before the applicationContext was initialized.");
+ return;
+ }
+
+ Intent intent = new Intent(action);
+
+ if (flags != null) {
+ intent.addFlags(flags);
+ }
+ if (!TextUtils.isEmpty(category)) {
+ intent.addCategory(category);
+ }
+ if (data != null) {
+ intent.setData(data);
+ }
+ if (arguments != null) {
+ intent.putExtras(arguments);
+ }
+ if (!TextUtils.isEmpty(packageName)) {
+ intent.setPackage(packageName);
+ if (intent.resolveActivity(applicationContext.getPackageManager()) == null) {
+ Log.i(TAG, "Cannot resolve explicit intent - ignoring package");
+ intent.setPackage(null);
+ }
+ }
+ if (componentName != null) {
+ intent.setComponent(componentName);
+ }
+
+ Log.v(TAG, "Sending intent " + intent);
+ if (activity != null) {
+ activity.startActivity(intent);
+ } else {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ applicationContext.startActivity(intent);
+ }
+ }
+
+ /** Caches the given {@code activity} to use for {@link #send}. */
+ void setActivity(@Nullable Activity activity) {
+ this.activity = activity;
+ }
+
+ /** Caches the given {@code applicationContext} to use for {@link #send}. */
+ void setApplicationContext(@Nullable Context applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java
new file mode 100644
index 000000000000..abbefc89f930
--- /dev/null
+++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java
@@ -0,0 +1,171 @@
+package io.flutter.plugins.androidintent;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.MethodChannel.Result;
+import java.util.ArrayList;
+import java.util.Map;
+
+/** Forwards incoming {@link MethodCall}s to {@link IntentSender#send}. */
+public final class MethodCallHandlerImpl implements MethodCallHandler {
+ private static final String TAG = "MethodCallHandlerImpl";
+ private final IntentSender sender;
+ private @Nullable MethodChannel methodChannel;
+
+ /**
+ * Uses the given {@code sender} for all incoming calls.
+ *
+ *
This assumes that the sender's context and activity state are managed elsewhere and
+ * correctly initialized before being sent here.
+ */
+ MethodCallHandlerImpl(IntentSender sender) {
+ this.sender = sender;
+ }
+
+ /**
+ * Registers this instance as a method call handler on the given {@code messenger}.
+ *
+ *
Stops any previously started and unstopped calls.
+ *
+ *
This should be cleaned with {@link #stopListening} once the messenger is disposed of.
+ */
+ void startListening(BinaryMessenger messenger) {
+ if (methodChannel != null) {
+ Log.wtf(TAG, "Setting a method call handler before the last was disposed.");
+ stopListening();
+ }
+
+ methodChannel = new MethodChannel(messenger, "plugins.flutter.io/android_intent");
+ methodChannel.setMethodCallHandler(this);
+ }
+
+ /**
+ * Clears this instance from listening to method calls.
+ *
+ *
Does nothing is {@link #startListening} hasn't been called, or if we're already stopped.
+ */
+ void stopListening() {
+ if (methodChannel == null) {
+ Log.d(TAG, "Tried to stop listening when no methodChannel had been initialized.");
+ return;
+ }
+
+ methodChannel.setMethodCallHandler(null);
+ methodChannel = null;
+ }
+
+ /**
+ * Parses the incoming call and forwards it to the cached {@link IntentSender}.
+ *
+ *
Always calls {@code result#success}.
+ */
+ @Override
+ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
+ String action = convertAction((String) call.argument("action"));
+ Integer flags = call.argument("flags");
+ String category = call.argument("category");
+ Uri data = call.argument("data") != null ? Uri.parse((String) call.argument("data")) : null;
+ Bundle arguments = convertArguments((Map) call.argument("arguments"));
+ String packageName = call.argument("package");
+ ComponentName componentName =
+ (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty((String) call.argument("component")))
+ ? new ComponentName(packageName, (String) call.argument("component"))
+ : null;
+
+ sender.send(action, flags, category, data, arguments, packageName, componentName);
+
+ result.success(null);
+ }
+
+ private static String convertAction(String action) {
+ switch (action) {
+ case "action_view":
+ return Intent.ACTION_VIEW;
+ case "action_voice":
+ return Intent.ACTION_VOICE_COMMAND;
+ case "settings":
+ return Settings.ACTION_SETTINGS;
+ case "action_location_source_settings":
+ return Settings.ACTION_LOCATION_SOURCE_SETTINGS;
+ case "action_application_details_settings":
+ return Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
+ default:
+ return action;
+ }
+ }
+
+ private static Bundle convertArguments(Map arguments) {
+ Bundle bundle = new Bundle();
+ if (arguments == null) {
+ return bundle;
+ }
+ for (String key : arguments.keySet()) {
+ Object value = arguments.get(key);
+ if (value instanceof Integer) {
+ bundle.putInt(key, (Integer) value);
+ } else if (value instanceof String) {
+ bundle.putString(key, (String) value);
+ } else if (value instanceof Boolean) {
+ bundle.putBoolean(key, (Boolean) value);
+ } else if (value instanceof Double) {
+ bundle.putDouble(key, (Double) value);
+ } else if (value instanceof Long) {
+ bundle.putLong(key, (Long) value);
+ } else if (value instanceof byte[]) {
+ bundle.putByteArray(key, (byte[]) value);
+ } else if (value instanceof int[]) {
+ bundle.putIntArray(key, (int[]) value);
+ } else if (value instanceof long[]) {
+ bundle.putLongArray(key, (long[]) value);
+ } else if (value instanceof double[]) {
+ bundle.putDoubleArray(key, (double[]) value);
+ } else if (isTypedArrayList(value, Integer.class)) {
+ bundle.putIntegerArrayList(key, (ArrayList) value);
+ } else if (isTypedArrayList(value, String.class)) {
+ bundle.putStringArrayList(key, (ArrayList) value);
+ } else if (isStringKeyedMap(value)) {
+ bundle.putBundle(key, convertArguments((Map) value));
+ } else {
+ throw new UnsupportedOperationException("Unsupported type " + value);
+ }
+ }
+ return bundle;
+ }
+
+ private static boolean isTypedArrayList(Object value, Class> type) {
+ if (!(value instanceof ArrayList)) {
+ return false;
+ }
+ ArrayList list = (ArrayList) value;
+ for (Object o : list) {
+ if (!(o == null || type.isInstance(o))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isStringKeyedMap(Object value) {
+ if (!(value instanceof Map)) {
+ return false;
+ }
+ Map map = (Map) value;
+ for (Object key : map.keySet()) {
+ if (!(key == null || key instanceof String)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java
new file mode 100644
index 000000000000..19392b04084f
--- /dev/null
+++ b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java
@@ -0,0 +1,197 @@
+package io.flutter.plugins.androidintent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import androidx.test.core.app.ApplicationProvider;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel.Result;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class MethodCallHandlerImplTest {
+ private static final String CHANNEL_NAME = "plugins.flutter.io/android_intent";
+ private Context context;
+ private IntentSender sender;
+ private MethodCallHandlerImpl methodCallHandler;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ sender = new IntentSender(null, null);
+ methodCallHandler = new MethodCallHandlerImpl(sender);
+ }
+
+ @Test
+ public void startListening_registersChannel() {
+ BinaryMessenger messenger = mock(BinaryMessenger.class);
+
+ methodCallHandler.startListening(messenger);
+
+ verify(messenger, times(1))
+ .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class));
+ }
+
+ @Test
+ public void startListening_unregistersExistingChannel() {
+ BinaryMessenger firstMessenger = mock(BinaryMessenger.class);
+ BinaryMessenger secondMessenger = mock(BinaryMessenger.class);
+ methodCallHandler.startListening(firstMessenger);
+
+ methodCallHandler.startListening(secondMessenger);
+
+ // Unregisters the first and then registers the second.
+ verify(firstMessenger, times(1)).setMessageHandler(CHANNEL_NAME, null);
+ verify(secondMessenger, times(1))
+ .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class));
+ }
+
+ @Test
+ public void stopListening_unregistersExistingChannel() {
+ BinaryMessenger messenger = mock(BinaryMessenger.class);
+ methodCallHandler.startListening(messenger);
+
+ methodCallHandler.stopListening();
+
+ verify(messenger, times(1)).setMessageHandler(CHANNEL_NAME, null);
+ }
+
+ @Test
+ public void stopListening_doesNothingWhenUnset() {
+ BinaryMessenger messenger = mock(BinaryMessenger.class);
+
+ methodCallHandler.stopListening();
+
+ verify(messenger, never()).setMessageHandler(CHANNEL_NAME, null);
+ }
+
+ @Test
+ public void onMethodCall_doesNothingWhenContextIsNull() {
+ Result result = mock(Result.class);
+ Map args = new HashMap<>();
+ args.put("action", "foo");
+
+ methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+
+ // No matter what, should always succeed.
+ verify(result, times(1)).success(null);
+ assertNull(shadowOf((Application) context).getNextStartedActivity());
+ }
+
+ @Test
+ public void onMethodCall_setsAction() {
+ sender.setApplicationContext(context);
+ Map args = new HashMap<>();
+ args.put("action", "foo");
+ Result result = mock(Result.class);
+
+ methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+
+ verify(result, times(1)).success(null);
+ Intent intent = shadowOf((Application) context).getNextStartedActivity();
+ assertNotNull(intent);
+ assertEquals("foo", intent.getAction());
+ }
+
+ @Test
+ public void onMethodCall_setsNewTaskFlagWithApplicationContext() {
+ sender.setApplicationContext(context);
+ Map args = new HashMap<>();
+ args.put("action", "foo");
+ Result result = mock(Result.class);
+
+ methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+
+ verify(result, times(1)).success(null);
+ Intent intent = shadowOf((Application) context).getNextStartedActivity();
+ assertNotNull(intent);
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, intent.getFlags());
+ }
+
+ @Test
+ public void onMethodCall_addsFlags() {
+ sender.setApplicationContext(context);
+ Map args = new HashMap<>();
+ args.put("action", "foo");
+ Integer requestFlags = Intent.FLAG_FROM_BACKGROUND;
+ args.put("flags", requestFlags);
+ Result result = mock(Result.class);
+
+ methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+
+ verify(result, times(1)).success(null);
+ Intent intent = shadowOf((Application) context).getNextStartedActivity();
+ assertNotNull(intent);
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK | requestFlags, intent.getFlags());
+ }
+
+ @Test
+ public void onMethodCall_addsCategory() {
+ sender.setApplicationContext(context);
+ Map args = new HashMap<>();
+ args.put("action", "foo");
+ String category = "bar";
+ args.put("category", category);
+ Result result = mock(Result.class);
+
+ methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+
+ verify(result, times(1)).success(null);
+ Intent intent = shadowOf((Application) context).getNextStartedActivity();
+ assertNotNull(intent);
+ assertTrue(intent.getCategories().contains(category));
+ }
+
+ @Test
+ public void onMethodCall_setsData() {
+ sender.setApplicationContext(context);
+ Map args = new HashMap<>();
+ args.put("action", "foo");
+ Uri data = Uri.parse("http://flutter.dev");
+ args.put("data", data.toString());
+ Result result = mock(Result.class);
+
+ methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+
+ verify(result, times(1)).success(null);
+ Intent intent = shadowOf((Application) context).getNextStartedActivity();
+ assertNotNull(intent);
+ assertEquals(data, intent.getData());
+ }
+
+ @Test
+ public void onMethodCall_clearsInvalidPackageNames() {
+ sender.setApplicationContext(context);
+ Map args = new HashMap<>();
+ args.put("action", "foo");
+ args.put("packageName", "invalid");
+ Result result = mock(Result.class);
+
+ methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+
+ verify(result, times(1)).success(null);
+ Intent intent = shadowOf((Application) context).getNextStartedActivity();
+ assertNotNull(intent);
+ assertNull(intent.getPackage());
+ }
+}
diff --git a/packages/android_intent/example/android/app/src/main/AndroidManifest.xml b/packages/android_intent/example/android/app/src/main/AndroidManifest.xml
index ce2fbe4a64a8..7d6fcd44834f 100644
--- a/packages/android_intent/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/android_intent/example/android/app/src/main/AndroidManifest.xml
@@ -1,29 +1,41 @@
+ package="io.flutter.plugins.androidintentexample">
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
diff --git a/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/EmbeddingV1Activity.java b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/EmbeddingV1Activity.java
new file mode 100644
index 000000000000..95dc41a02ef7
--- /dev/null
+++ b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/EmbeddingV1Activity.java
@@ -0,0 +1,17 @@
+// Copyright 2017 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.androidintentexample;
+
+import android.os.Bundle;
+import io.flutter.app.FlutterActivity;
+import io.flutter.plugins.GeneratedPluginRegistrant;
+
+public class EmbeddingV1Activity extends FlutterActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ GeneratedPluginRegistrant.registerWith(this);
+ }
+}
diff --git a/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java
index 4af83acdf1cb..56e0bab207d4 100644
--- a/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java
+++ b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java
@@ -1,17 +1,12 @@
-// Copyright 2017 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.androidintentexample;
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.plugins.androidintent.AndroidIntentPlugin;
public class MainActivity extends FlutterActivity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
+ public void configureFlutterEngine(FlutterEngine flutterEngine) {
+ flutterEngine.getPlugins().add(new AndroidIntentPlugin());
}
}
diff --git a/packages/android_intent/example/android/gradle.properties b/packages/android_intent/example/android/gradle.properties
index 8bd86f680510..7be3d8b46841 100644
--- a/packages/android_intent/example/android/gradle.properties
+++ b/packages/android_intent/example/android/gradle.properties
@@ -1 +1,2 @@
org.gradle.jvmargs=-Xmx1536M
+android.enableR8=true
diff --git a/packages/android_intent/pubspec.yaml b/packages/android_intent/pubspec.yaml
index 2bdf002f3ea8..bcb73cf76636 100644
--- a/packages/android_intent/pubspec.yaml
+++ b/packages/android_intent/pubspec.yaml
@@ -2,7 +2,7 @@ name: android_intent
description: Flutter plugin for launching Android Intents. Not supported on iOS.
author: Flutter Team
homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent
-version: 0.3.3+3
+version: 0.3.4
flutter:
plugin: