[quick_actions] Android support only calling initialize once (#4204)

Fixes flutter/flutter#87259
diff --git a/packages/quick_actions/quick_actions/CHANGELOG.md b/packages/quick_actions/quick_actions/CHANGELOG.md
index 4f89438..5d040f4 100644
--- a/packages/quick_actions/quick_actions/CHANGELOG.md
+++ b/packages/quick_actions/quick_actions/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.0+5
+
+* Support only calling initialize once.
+
 ## 0.6.0+4
 
 * Remove references to the Android V1 embedding.
diff --git a/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java b/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java
index 4652830..2d89352 100644
--- a/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java
+++ b/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java
@@ -20,9 +20,8 @@
 import java.util.Map;
 
 class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
-
+  protected static final String EXTRA_ACTION = "some unique action key";
   private static final String CHANNEL_ID = "plugins.flutter.io/quick_actions";
-  private static final String EXTRA_ACTION = "some unique action key";
 
   private final Context context;
   private Activity activity;
diff --git a/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java b/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java
index ab34313..b2f80ad 100644
--- a/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java
+++ b/packages/quick_actions/quick_actions/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java
@@ -6,14 +6,17 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
 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.BinaryMessenger;
 import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.PluginRegistry.NewIntentListener;
 
 /** QuickActionsPlugin */
-public class QuickActionsPlugin implements FlutterPlugin, ActivityAware {
+public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewIntentListener {
   private static final String CHANNEL_ID = "plugins.flutter.io/quick_actions";
 
   private MethodChannel channel;
@@ -43,6 +46,8 @@
   @Override
   public void onAttachedToActivity(ActivityPluginBinding binding) {
     handler.setActivity(binding.getActivity());
+    binding.addOnNewIntentListener(this);
+    onNewIntent(binding.getActivity().getIntent());
   }
 
   @Override
@@ -52,6 +57,7 @@
 
   @Override
   public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
+    binding.removeOnNewIntentListener(this);
     onAttachedToActivity(binding);
   }
 
@@ -60,6 +66,19 @@
     onDetachedFromActivity();
   }
 
+  @Override
+  public boolean onNewIntent(Intent intent) {
+    // Do nothing for anything lower than API 25 as the functionality isn't supported.
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
+      return false;
+    }
+    // Notify the Dart side if the launch intent has the intent extra relevant to quick actions.
+    if (intent.hasExtra(MethodCallHandlerImpl.EXTRA_ACTION) && channel != null) {
+      channel.invokeMethod("launch", intent.getStringExtra(MethodCallHandlerImpl.EXTRA_ACTION));
+    }
+    return false;
+  }
+
   private void setupChannel(BinaryMessenger messenger, Context context, Activity activity) {
     channel = new MethodChannel(messenger, CHANNEL_ID);
     handler = new MethodCallHandlerImpl(context, activity);
diff --git a/packages/quick_actions/quick_actions/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java b/packages/quick_actions/quick_actions/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java
index d437444..208a119 100644
--- a/packages/quick_actions/quick_actions/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java
+++ b/packages/quick_actions/quick_actions/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java
@@ -4,17 +4,30 @@
 
 package io.flutter.plugins.quickactions;
 
+import static io.flutter.plugins.quickactions.MethodCallHandlerImpl.EXTRA_ACTION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding;
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.StandardMethodCodec;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.nio.ByteBuffer;
+import org.junit.After;
 import org.junit.Test;
+import org.mockito.internal.util.reflection.FieldSetter;
 
 public class QuickActionsTest {
   private static class TestBinaryMessenger implements BinaryMessenger {
@@ -42,6 +55,10 @@
     }
   }
 
+  static final int SUPPORTED_BUILD = 25;
+  static final int UNSUPPORTED_BUILD = 24;
+  static final String SHORTCUT_TYPE = "action_one";
+
   @Test
   public void canAttachToEngine() {
     final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger();
@@ -51,4 +68,98 @@
     final QuickActionsPlugin plugin = new QuickActionsPlugin();
     plugin.onAttachedToEngine(mockPluginBinding);
   }
+
+  @Test
+  public void onAttachedToActivity_buildVersionSupported_invokesLaunchMethod()
+      throws NoSuchFieldException, IllegalAccessException {
+    // Arrange
+    final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger();
+    final QuickActionsPlugin plugin = new QuickActionsPlugin();
+    setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin);
+    setBuildVersion(SUPPORTED_BUILD);
+    FieldSetter.setField(
+        plugin,
+        QuickActionsPlugin.class.getDeclaredField("handler"),
+        mock(MethodCallHandlerImpl.class));
+    final Intent mockIntent = createMockIntentWithQuickActionExtra();
+    final Activity mockMainActivity = mock(Activity.class);
+    when(mockMainActivity.getIntent()).thenReturn(mockIntent);
+    final ActivityPluginBinding mockActivityPluginBinding = mock(ActivityPluginBinding.class);
+    when(mockActivityPluginBinding.getActivity()).thenReturn(mockMainActivity);
+
+    // Act
+    plugin.onAttachedToActivity(mockActivityPluginBinding);
+
+    // Assert
+    assertNotNull(testBinaryMessenger.lastMethodCall);
+    assertEquals(testBinaryMessenger.lastMethodCall.method, "launch");
+    assertEquals(testBinaryMessenger.lastMethodCall.arguments, SHORTCUT_TYPE);
+  }
+
+  @Test
+  public void onNewIntent_buildVersionUnsupported_doesNotInvokeMethod()
+      throws NoSuchFieldException, IllegalAccessException {
+    // Arrange
+    final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger();
+    final QuickActionsPlugin plugin = new QuickActionsPlugin();
+    setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin);
+    setBuildVersion(UNSUPPORTED_BUILD);
+    final Intent mockIntent = createMockIntentWithQuickActionExtra();
+
+    // Act
+    final boolean onNewIntentReturn = plugin.onNewIntent(mockIntent);
+
+    // Assert
+    assertNull(testBinaryMessenger.lastMethodCall);
+    assertFalse(onNewIntentReturn);
+  }
+
+  @Test
+  public void onNewIntent_buildVersionSupported_invokesLaunchMethod()
+      throws NoSuchFieldException, IllegalAccessException {
+    // Arrange
+    final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger();
+    final QuickActionsPlugin plugin = new QuickActionsPlugin();
+    setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin);
+    setBuildVersion(SUPPORTED_BUILD);
+    final Intent mockIntent = createMockIntentWithQuickActionExtra();
+
+    // Act
+    final boolean onNewIntentReturn = plugin.onNewIntent(mockIntent);
+
+    // Assert
+    assertNotNull(testBinaryMessenger.lastMethodCall);
+    assertEquals(testBinaryMessenger.lastMethodCall.method, "launch");
+    assertEquals(testBinaryMessenger.lastMethodCall.arguments, SHORTCUT_TYPE);
+    assertFalse(onNewIntentReturn);
+  }
+
+  private void setUpMessengerAndFlutterPluginBinding(
+      TestBinaryMessenger testBinaryMessenger, QuickActionsPlugin plugin) {
+    final FlutterPluginBinding mockPluginBinding = mock(FlutterPluginBinding.class);
+    when(mockPluginBinding.getBinaryMessenger()).thenReturn(testBinaryMessenger);
+    plugin.onAttachedToEngine(mockPluginBinding);
+  }
+
+  private Intent createMockIntentWithQuickActionExtra() {
+    final Intent mockIntent = mock(Intent.class);
+    when(mockIntent.hasExtra(EXTRA_ACTION)).thenReturn(true);
+    when(mockIntent.getStringExtra(EXTRA_ACTION)).thenReturn(QuickActionsTest.SHORTCUT_TYPE);
+    return mockIntent;
+  }
+
+  private void setBuildVersion(int buildVersion)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field buildSdkField = Build.VERSION.class.getField("SDK_INT");
+    buildSdkField.setAccessible(true);
+    final Field modifiersField = Field.class.getDeclaredField("modifiers");
+    modifiersField.setAccessible(true);
+    modifiersField.setInt(buildSdkField, buildSdkField.getModifiers() & ~Modifier.FINAL);
+    buildSdkField.set(null, buildVersion);
+  }
+
+  @After
+  public void tearDown() throws NoSuchFieldException, IllegalAccessException {
+    setBuildVersion(0);
+  }
 }
diff --git a/packages/quick_actions/quick_actions/lib/quick_actions.dart b/packages/quick_actions/quick_actions/lib/quick_actions.dart
index 6907f25..7d3d4ad 100644
--- a/packages/quick_actions/quick_actions/lib/quick_actions.dart
+++ b/packages/quick_actions/quick_actions/lib/quick_actions.dart
@@ -16,7 +16,7 @@
 
   /// Initializes this plugin.
   ///
-  /// Call this once before any further interaction with the the plugin.
+  /// Call this once before any further interaction with the plugin.
   Future<void> initialize(QuickActionHandler handler) async =>
       QuickActionsPlatform.instance.initialize(handler);
 
diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml
index 657c2f0..e52ab51 100644
--- a/packages/quick_actions/quick_actions/pubspec.yaml
+++ b/packages/quick_actions/quick_actions/pubspec.yaml
@@ -3,7 +3,7 @@
   Quick Actions on iOS and App Shortcuts on Android.
 repository: https://github.com/flutter/plugins/tree/master/packages/quick_actions
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+quick_actions%22
-version: 0.6.0+4
+version: 0.6.0+5
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/quick_actions/quick_actions_platform_interface/lib/platform_interface/quick_actions_platform.dart b/packages/quick_actions/quick_actions_platform_interface/lib/platform_interface/quick_actions_platform.dart
index b15fb8b..2e06935 100644
--- a/packages/quick_actions/quick_actions_platform_interface/lib/platform_interface/quick_actions_platform.dart
+++ b/packages/quick_actions/quick_actions_platform_interface/lib/platform_interface/quick_actions_platform.dart
@@ -38,7 +38,7 @@
 
   /// Initializes this plugin.
   ///
-  /// Call this once before any further interaction with the the plugin.
+  /// Call this once before any further interaction with the plugin.
   Future<void> initialize(QuickActionHandler handler) async {
     throw UnimplementedError("initialize() has not been implemented.");
   }