From f4d657fe5cb0bf19076af8e096dc4b64e7912566 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Sat, 12 Mar 2022 09:24:16 +0800 Subject: [PATCH 01/20] [quick_actions] Support keeping state when App Shortcut is triggered in Android --- .../quick_actions/quick_actions/CHANGELOG.md | 4 + .../quick_actions/quick_actions/pubspec.yaml | 2 +- .../android/src/main/AndroidManifest.xml | 8 +- .../quickactions/MethodCallHandlerImpl.java | 6 +- .../quickactions/QuickActionsPlugin.java | 1 + .../example/android/app/build.gradle | 11 +- .../quickactionsexample/QuickActionsTest.java | 140 +++++++++++++++++- .../plugins/quickactionsexample/Shortcut.java | 49 ++++++ 8 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java diff --git a/packages/quick_actions/quick_actions/CHANGELOG.md b/packages/quick_actions/quick_actions/CHANGELOG.md index e95d56c53e9d..019aaae92b85 100644 --- a/packages/quick_actions/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/quick_actions/CHANGELOG.md @@ -2,6 +2,10 @@ * Updates minimum Flutter version to 2.8. +## 0.7.0 + +* Allow Android to trigger quick actions without restarting the app. + ## 0.6.0+10 * Moves Android and iOS implementations to federated packages. diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index 8ef2d3ab4e02..e91386ba1a49 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for creating shortcuts on home screen, also known as Quick Actions on iOS and App Shortcuts on Android. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/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+10 +version: 0.7.0 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml b/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml index 5b02f6d8aef2..ea0e991cc2cb 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml +++ b/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ - + + + diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java index 6316e8428288..3a58143f12cf 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java @@ -74,7 +74,8 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { final boolean didSucceed = dynamicShortcutsSet; - // TODO(camsim99): Move re-dispatch below to background thread when Flutter 2.8+ is stable. + // TODO(camsim99): Move re-dispatch below to background thread when Flutter 2.8+ is + // stable. uiThreadExecutor.execute( () -> { if (didSucceed) { @@ -162,8 +163,7 @@ private Intent getIntentToOpenMainActivity(String type) { .getLaunchIntentForPackage(packageName) .setAction(Intent.ACTION_RUN) .putExtra(EXTRA_ACTION, type) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } private static class UiThreadExecutor implements Executor { diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java index 99ce0f8426a0..96c42c5e57d5 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java @@ -74,6 +74,7 @@ public boolean onNewIntent(Intent intent) { } // 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("getLaunchAction", null); channel.invokeMethod("launch", intent.getStringExtra(MethodCallHandlerImpl.EXTRA_ACTION)); } return false; diff --git a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle index 54f2e59bacf7..2064dff93deb 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle +++ b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle @@ -24,6 +24,8 @@ if (flutterVersionName == null) { apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +def androidXTestVersion = '1.2.0' + android { compileSdkVersion 31 @@ -53,7 +55,12 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.2.0' + api "androidx.test:core:$androidXTestVersion" + + androidTestImplementation "androidx.test:runner:$androidXTestVersion" + androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.0.0' + androidTestImplementation 'org.mockito:mockito-core:4.3.1' + androidTestImplementation 'org.mockito:mockito-android:4.3.1' } diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 9d2fed13fc27..ebb51d4a282d 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -4,13 +4,47 @@ package io.flutter.plugins.quickactionsexample; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.util.Log; +import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ActivityScenario; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.*; import io.flutter.plugins.quickactions.QuickActionsPlugin; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +@RunWith(AndroidJUnit4.class) public class QuickActionsTest { + private Context context; + private UiDevice device; + private ActivityScenario scenario; + + @Before + public void setUp() { + context = ApplicationProvider.getApplicationContext(); + device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + scenario = ensureAppRunToView(); + } + + @After + public void tearDown() { + scenario.close(); + Log.i(QuickActionsTest.class.getSimpleName(), "Run to completion"); + } + @Test public void imagePickerPluginIsAdded() { final ActivityScenario scenario = @@ -20,4 +54,108 @@ public void imagePickerPluginIsAdded() { assertTrue(activity.engine.getPlugins().has(QuickActionsPlugin.class)); }); } + + @Test + public void appShortcutsAreCreated() { + // Arrange + List expectedShortcuts = createMockShortcuts(); + + // Act + ShortcutManager shortcutManager = + (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); + List dynamicShortcuts = shortcutManager.getDynamicShortcuts(); + Object[] shortcuts = dynamicShortcuts.stream().map(Shortcut::new).toArray(); + + // Assert the app shortcuts defined in ../lib/main.dart. + assertFalse(dynamicShortcuts.isEmpty()); + assertEquals(2, dynamicShortcuts.size()); + assertArrayEquals(expectedShortcuts.toArray(), shortcuts); + } + + @Test + public void appShortcutExistsAfterLongPressingAppIcon() throws UiObjectNotFoundException { + // Arrange + List shortcuts = createMockShortcuts(); + String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); + + // Act + findAppIcon(device, appName).longClick(); + + // Assert + for (Shortcut shortcut : shortcuts) { + Assert.assertTrue( + "The specified shortcut label '" + shortcut.shortLabel + "' does not exist.", + device.hasObject(By.text(shortcut.shortLabel))); + } + } + + @Test + public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundException { + // Arrange + List shortcuts = createMockShortcuts(); + String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); + Shortcut firstShortcut = shortcuts.get(0); + AtomicReference initialActivity = new AtomicReference<>(); + scenario.onActivity(initialActivity::set); + + // Act + findAppIcon(device, appName).longClick(); + UiObject appShortcut = device.findObject(new UiSelector().text(firstShortcut.shortLabel)); + appShortcut.clickAndWaitForNewWindow(); + AtomicReference currentActivity = new AtomicReference<>(); + scenario.onActivity(currentActivity::set); + + // Assert + Assert.assertTrue( + "AppShortcut:" + firstShortcut.type + " does not launch the correct activity", + // We can only find the shortcut type in content description while inspecting it in Ui + // Automator Viewer. + device.hasObject(By.desc(firstShortcut.type))); + // This is Android SingleTop behavior in which Android does not destroy the initial activity and + // launch a new activity. + Assert.assertEquals(initialActivity.get(), currentActivity.get()); + } + + private List createMockShortcuts() { + List expectedShortcuts = new ArrayList<>(); + String actionOneLocalizedTitle = "Action one"; + expectedShortcuts.add( + new Shortcut("action_one", actionOneLocalizedTitle, actionOneLocalizedTitle)); + + String actionTwoLocalizedTitle = "Action two"; + expectedShortcuts.add( + new Shortcut("action_two", actionTwoLocalizedTitle, actionTwoLocalizedTitle)); + + return expectedShortcuts; + } + + private ActivityScenario ensureAppRunToView() { + final ActivityScenario scenario = + ActivityScenario.launch(QuickActionsTestActivity.class); + scenario.moveToState(Lifecycle.State.STARTED); + return scenario; + } + + private UiObject findAppIcon(UiDevice device, String appName) throws UiObjectNotFoundException { + device.pressHome(); + + // Swipe up to open App Drawer + UiScrollable homeView = new UiScrollable(new UiSelector().scrollable(true)); + homeView.scrollForward(); + + if (!device.hasObject(By.text(appName))) { + Log.i( + QuickActionsTest.class.getSimpleName(), + "Attempting to scroll App Drawer for App Icon..."); + UiScrollable appDrawer = new UiScrollable(new UiSelector().scrollable(true)); + // The scrollTextIntoView scrolls to the beginning before performing searching scroll; this + // causes an issue in a scenario where the view is already in the beginning. In this case, it + // scrolls back to home view. Therefore, we perform a dummy forward scroll to ensure it is not + // in the beginning. + appDrawer.scrollForward(); + appDrawer.scrollTextIntoView(appName); + } + + return device.findObject(new UiSelector().text(appName)); + } } diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java new file mode 100644 index 000000000000..3a3aa232a68c --- /dev/null +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java @@ -0,0 +1,49 @@ +// 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.quickactionsexample; + +import android.content.pm.ShortcutInfo; +import java.util.Objects; + +class Shortcut { + final String type; + final String shortLabel; + final String longLabel; + String icon; + + public Shortcut(ShortcutInfo shortcutInfo) { + this.type = shortcutInfo.getId(); + this.shortLabel = shortcutInfo.getShortLabel().toString(); + this.longLabel = shortcutInfo.getLongLabel().toString(); + } + + public Shortcut(String type, String shortLabel, String longLabel) { + this.type = type; + this.shortLabel = shortLabel; + this.longLabel = longLabel; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Shortcut shortcut = (Shortcut) o; + + if (!type.equals(shortcut.type)) return false; + if (!shortLabel.equals(shortcut.shortLabel)) return false; + if (!longLabel.equals(shortcut.longLabel)) return false; + return Objects.equals(icon, shortcut.icon); + } + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + shortLabel.hashCode(); + result = 31 * result + longLabel.hashCode(); + result = 31 * result + (icon != null ? icon.hashCode() : 0); + return result; + } +} From 42ca2da575bb4e24e9b378107304c31f5141e369 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Fri, 29 Apr 2022 17:42:32 +0800 Subject: [PATCH 02/20] Remove redundant comments in QuickActionsTest --- .../plugins/quickactionsexample/QuickActionsTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index ebb51d4a282d..2ed3294a54c3 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -57,10 +57,8 @@ public void imagePickerPluginIsAdded() { @Test public void appShortcutsAreCreated() { - // Arrange List expectedShortcuts = createMockShortcuts(); - // Act ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); List dynamicShortcuts = shortcutManager.getDynamicShortcuts(); @@ -74,14 +72,11 @@ public void appShortcutsAreCreated() { @Test public void appShortcutExistsAfterLongPressingAppIcon() throws UiObjectNotFoundException { - // Arrange List shortcuts = createMockShortcuts(); String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); - // Act findAppIcon(device, appName).longClick(); - // Assert for (Shortcut shortcut : shortcuts) { Assert.assertTrue( "The specified shortcut label '" + shortcut.shortLabel + "' does not exist.", From d00c370906306f880bf93365446a08c4749c0a64 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Fri, 29 Apr 2022 17:50:50 +0800 Subject: [PATCH 03/20] Fix incorrect test method name in QuickActionsTest --- .../flutter/plugins/quickactionsexample/QuickActionsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 2ed3294a54c3..25c857b8e646 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -46,7 +46,7 @@ public void tearDown() { } @Test - public void imagePickerPluginIsAdded() { + public void quickActionPluginIsAdded() { final ActivityScenario scenario = ActivityScenario.launch(QuickActionsTestActivity.class); scenario.onActivity( From b61dc9c1dc23cdfe1cade3114484159fa3066342 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Fri, 29 Apr 2022 19:41:34 +0800 Subject: [PATCH 04/20] Revert removal of xml metadata --- .../quick_actions_android/android/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml b/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml index ea0e991cc2cb..5ec81f08ec6a 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml +++ b/packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - From 661bc5dcb9f7eba444d7f5c5e042df6a0c98b1c5 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Sat, 30 Apr 2022 08:55:52 +0800 Subject: [PATCH 05/20] Refactor to use ShortcutInfo and remove custom Shortcut class --- .../quickactionsexample/QuickActionsTest.java | 47 +++++++++++------- .../plugins/quickactionsexample/Shortcut.java | 49 ------------------- 2 files changed, 30 insertions(+), 66 deletions(-) delete mode 100644 packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 25c857b8e646..b1ef0df7c024 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -57,69 +57,82 @@ public void quickActionPluginIsAdded() { @Test public void appShortcutsAreCreated() { - List expectedShortcuts = createMockShortcuts(); + List expectedShortcuts = createMockShortcuts(); ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); List dynamicShortcuts = shortcutManager.getDynamicShortcuts(); - Object[] shortcuts = dynamicShortcuts.stream().map(Shortcut::new).toArray(); // Assert the app shortcuts defined in ../lib/main.dart. assertFalse(dynamicShortcuts.isEmpty()); - assertEquals(2, dynamicShortcuts.size()); - assertArrayEquals(expectedShortcuts.toArray(), shortcuts); + assertEquals(expectedShortcuts.size(), dynamicShortcuts.size()); + for (int i = 0; i < expectedShortcuts.size(); i++) { + ShortcutInfo expectedShortcut = expectedShortcuts.get(i); + ShortcutInfo dynamicShortcut = dynamicShortcuts.get(i); + + assertEquals(expectedShortcut.getId(), dynamicShortcut.getId()); + assertEquals(expectedShortcut.getShortLabel(), dynamicShortcut.getShortLabel()); + assertEquals(expectedShortcut.getLongLabel(), dynamicShortcut.getLongLabel()); + } } @Test public void appShortcutExistsAfterLongPressingAppIcon() throws UiObjectNotFoundException { - List shortcuts = createMockShortcuts(); + List shortcuts = createMockShortcuts(); String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); findAppIcon(device, appName).longClick(); - for (Shortcut shortcut : shortcuts) { + for (ShortcutInfo shortcut : shortcuts) { Assert.assertTrue( - "The specified shortcut label '" + shortcut.shortLabel + "' does not exist.", - device.hasObject(By.text(shortcut.shortLabel))); + "The specified shortcut label '" + shortcut.getShortLabel() + "' does not exist.", + device.hasObject(By.text(shortcut.getShortLabel().toString()))); } } @Test public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundException { // Arrange - List shortcuts = createMockShortcuts(); + List shortcuts = createMockShortcuts(); String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); - Shortcut firstShortcut = shortcuts.get(0); + ShortcutInfo firstShortcut = shortcuts.get(0); AtomicReference initialActivity = new AtomicReference<>(); scenario.onActivity(initialActivity::set); // Act findAppIcon(device, appName).longClick(); - UiObject appShortcut = device.findObject(new UiSelector().text(firstShortcut.shortLabel)); + UiObject appShortcut = device.findObject(new UiSelector().text(firstShortcut.getShortLabel().toString())); appShortcut.clickAndWaitForNewWindow(); AtomicReference currentActivity = new AtomicReference<>(); scenario.onActivity(currentActivity::set); // Assert Assert.assertTrue( - "AppShortcut:" + firstShortcut.type + " does not launch the correct activity", + "AppShortcut:" + firstShortcut.getId() + " does not launch the correct activity", // We can only find the shortcut type in content description while inspecting it in Ui // Automator Viewer. - device.hasObject(By.desc(firstShortcut.type))); + device.hasObject(By.desc(firstShortcut.getId()))); // This is Android SingleTop behavior in which Android does not destroy the initial activity and // launch a new activity. Assert.assertEquals(initialActivity.get(), currentActivity.get()); } - private List createMockShortcuts() { - List expectedShortcuts = new ArrayList<>(); + private List createMockShortcuts() { + List expectedShortcuts = new ArrayList<>(); + String actionOneLocalizedTitle = "Action one"; expectedShortcuts.add( - new Shortcut("action_one", actionOneLocalizedTitle, actionOneLocalizedTitle)); + new ShortcutInfo.Builder(context, "action_one") + .setShortLabel(actionOneLocalizedTitle) + .setLongLabel(actionOneLocalizedTitle) + .build()); String actionTwoLocalizedTitle = "Action two"; expectedShortcuts.add( - new Shortcut("action_two", actionTwoLocalizedTitle, actionTwoLocalizedTitle)); + new ShortcutInfo.Builder(context, "action_two") + .setShortLabel(actionTwoLocalizedTitle) + .setLongLabel(actionTwoLocalizedTitle) + .build()); return expectedShortcuts; } diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java deleted file mode 100644 index 3a3aa232a68c..000000000000 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/Shortcut.java +++ /dev/null @@ -1,49 +0,0 @@ -// 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.quickactionsexample; - -import android.content.pm.ShortcutInfo; -import java.util.Objects; - -class Shortcut { - final String type; - final String shortLabel; - final String longLabel; - String icon; - - public Shortcut(ShortcutInfo shortcutInfo) { - this.type = shortcutInfo.getId(); - this.shortLabel = shortcutInfo.getShortLabel().toString(); - this.longLabel = shortcutInfo.getLongLabel().toString(); - } - - public Shortcut(String type, String shortLabel, String longLabel) { - this.type = type; - this.shortLabel = shortLabel; - this.longLabel = longLabel; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Shortcut shortcut = (Shortcut) o; - - if (!type.equals(shortcut.type)) return false; - if (!shortLabel.equals(shortcut.shortLabel)) return false; - if (!longLabel.equals(shortcut.longLabel)) return false; - return Objects.equals(icon, shortcut.icon); - } - - @Override - public int hashCode() { - int result = type.hashCode(); - result = 31 * result + shortLabel.hashCode(); - result = 31 * result + longLabel.hashCode(); - result = 31 * result + (icon != null ? icon.hashCode() : 0); - return result; - } -} From 28a82b37fbe9eda93db23d1b536a2a4d611ee7e1 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Sat, 30 Apr 2022 10:19:17 +0800 Subject: [PATCH 06/20] Format code --- .../plugins/quickactionsexample/QuickActionsTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index b1ef0df7c024..11854e6cab9f 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -1,6 +1,5 @@ // 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. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. package io.flutter.plugins.quickactionsexample; @@ -69,7 +68,7 @@ public void appShortcutsAreCreated() { for (int i = 0; i < expectedShortcuts.size(); i++) { ShortcutInfo expectedShortcut = expectedShortcuts.get(i); ShortcutInfo dynamicShortcut = dynamicShortcuts.get(i); - + assertEquals(expectedShortcut.getId(), dynamicShortcut.getId()); assertEquals(expectedShortcut.getShortLabel(), dynamicShortcut.getShortLabel()); assertEquals(expectedShortcut.getLongLabel(), dynamicShortcut.getLongLabel()); @@ -101,7 +100,8 @@ public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundExce // Act findAppIcon(device, appName).longClick(); - UiObject appShortcut = device.findObject(new UiSelector().text(firstShortcut.getShortLabel().toString())); + UiObject appShortcut = + device.findObject(new UiSelector().text(firstShortcut.getShortLabel().toString())); appShortcut.clickAndWaitForNewWindow(); AtomicReference currentActivity = new AtomicReference<>(); scenario.onActivity(currentActivity::set); From 8c754e3b9b79a2370a9db345689dde2754add0ac Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 4 May 2022 09:05:26 +0800 Subject: [PATCH 07/20] Correct license file formatting --- .../flutter/plugins/quickactionsexample/QuickActionsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 11854e6cab9f..465ba1275991 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -1,5 +1,6 @@ // 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. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. package io.flutter.plugins.quickactionsexample; From 84372e829e431e3bb1b6ff17815c1a7a4bee271c Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 4 May 2022 09:32:16 +0800 Subject: [PATCH 08/20] Set version in pubspec.yaml correctly --- packages/quick_actions/quick_actions/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index e91386ba1a49..edb2c6aef803 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for creating shortcuts on home screen, also known as Quick Actions on iOS and App Shortcuts on Android. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+quick_actions%22 -version: 0.7.0 +version: 0.6.1+11 environment: sdk: ">=2.14.0 <3.0.0" From 4c83cd44661ddaf19f942e2be441f3302ab2de04 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 4 May 2022 09:44:55 +0800 Subject: [PATCH 09/20] Fix import style that violates Google Java Style Guide --- .../plugins/quickactionsexample/QuickActionsTest.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 465ba1275991..b025279a19ec 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -4,7 +4,9 @@ package io.flutter.plugins.quickactionsexample; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import android.content.Context; import android.content.pm.ShortcutInfo; @@ -15,7 +17,12 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.uiautomator.*; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiObjectNotFoundException; +import androidx.test.uiautomator.UiScrollable; +import androidx.test.uiautomator.UiSelector; +import androidx.test.uiautomator.By; import io.flutter.plugins.quickactions.QuickActionsPlugin; import java.util.ArrayList; import java.util.List; From ad7b4a920f8618b7a2080077272582b0fc551269 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 4 May 2022 09:48:17 +0800 Subject: [PATCH 10/20] Fix calling Java MethodChannel from Java side --- .../plugins/quickactions/QuickActionsPlugin.java | 15 +++++++++++---- .../quickactionsexample/QuickActionsTest.java | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java index 96c42c5e57d5..b41087816889 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java @@ -7,6 +7,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.ShortcutManager; import android.os.Build; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; @@ -21,6 +22,7 @@ public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewInte private MethodChannel channel; private MethodCallHandlerImpl handler; + private Activity activity; /** * Plugin registration. @@ -45,9 +47,10 @@ public void onDetachedFromEngine(FlutterPluginBinding binding) { @Override public void onAttachedToActivity(ActivityPluginBinding binding) { - handler.setActivity(binding.getActivity()); + activity = binding.getActivity(); + handler.setActivity(activity); binding.addOnNewIntentListener(this); - onNewIntent(binding.getActivity().getIntent()); + onNewIntent(activity.getIntent()); } @Override @@ -74,8 +77,12 @@ public boolean onNewIntent(Intent intent) { } // 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("getLaunchAction", null); - channel.invokeMethod("launch", intent.getStringExtra(MethodCallHandlerImpl.EXTRA_ACTION)); + Context context = activity.getApplicationContext(); + ShortcutManager shortcutManager = + (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); + String shortcutId = intent.getStringExtra(MethodCallHandlerImpl.EXTRA_ACTION); + channel.invokeMethod("launch", shortcutId); + shortcutManager.reportShortcutUsed(shortcutId); } return false; } diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index b025279a19ec..48650d0ede1b 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -4,9 +4,9 @@ package io.flutter.plugins.quickactionsexample; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; import android.content.Context; import android.content.pm.ShortcutInfo; @@ -17,12 +17,12 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.By; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject; import androidx.test.uiautomator.UiObjectNotFoundException; import androidx.test.uiautomator.UiScrollable; import androidx.test.uiautomator.UiSelector; -import androidx.test.uiautomator.By; import io.flutter.plugins.quickactions.QuickActionsPlugin; import java.util.ArrayList; import java.util.List; From 24bdc10873305f4884ad1f6993bfe99a8acaa6f1 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 4 May 2022 11:26:39 +0800 Subject: [PATCH 11/20] Bump version to 0.6.1 --- packages/quick_actions/quick_actions/pubspec.yaml | 2 +- packages/quick_actions/quick_actions_android/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index edb2c6aef803..1f04e9cca0bb 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for creating shortcuts on home screen, also known as Quick Actions on iOS and App Shortcuts on Android. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/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.1+11 +version: 0.6.1 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/quick_actions/quick_actions_android/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index cf9971dca945..7cb274116536 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -2,7 +2,7 @@ name: quick_actions_android description: An implementation for the Android platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.6.0+9 +version: 0.6.1 environment: sdk: ">=2.15.0 <3.0.0" From 4784fae4427fdff2e1e9350f62c094bc9dd83c02 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 4 May 2022 11:32:05 +0800 Subject: [PATCH 12/20] Update CHANGELOG --- packages/quick_actions/quick_actions/CHANGELOG.md | 3 --- packages/quick_actions/quick_actions/pubspec.yaml | 2 +- packages/quick_actions/quick_actions_android/CHANGELOG.md | 6 +++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/quick_actions/quick_actions/CHANGELOG.md b/packages/quick_actions/quick_actions/CHANGELOG.md index 6646ae306921..5e29582ff638 100644 --- a/packages/quick_actions/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/quick_actions/CHANGELOG.md @@ -2,9 +2,6 @@ * Updates minimum Flutter version to 2.8. * Adds OS version support information to README. - -## 0.7.0 - * Allow Android to trigger quick actions without restarting the app. ## 0.6.0+10 diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index 1f04e9cca0bb..8ef2d3ab4e02 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for creating shortcuts on home screen, also known as Quick Actions on iOS and App Shortcuts on Android. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/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.1 +version: 0.6.0+10 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index 98e8cf5e333b..ccb48acf0b9d 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.1 + +* Allow Android to trigger quick actions without restarting the app. + ## 0.6.0+9 -* Switches to a package-internal implementation of the platform interface. \ No newline at end of file +* Switches to a package-internal implementation of the platform interface. From b2cff95e7ddfe403bddf728c82cc3bf5045a10d3 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 4 May 2022 12:21:43 +0800 Subject: [PATCH 13/20] Fix test failure in QuickActionsTest Fix the test failure by adding mock implementation for activity --- .../plugins/quickactions/QuickActionsTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java index d2e63b62f229..dc4b36e168db 100644 --- a/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java @@ -13,7 +13,9 @@ import static org.mockito.Mockito.when; import android.app.Activity; +import android.content.Context; import android.content.Intent; +import android.content.pm.ShortcutManager; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -86,6 +88,11 @@ public void onAttachedToActivity_buildVersionSupported_invokesLaunchMethod() when(mockMainActivity.getIntent()).thenReturn(mockIntent); final ActivityPluginBinding mockActivityPluginBinding = mock(ActivityPluginBinding.class); when(mockActivityPluginBinding.getActivity()).thenReturn(mockMainActivity); + final Context mockContext = mock(Context.class); + when(mockMainActivity.getApplicationContext()).thenReturn(mockContext); + final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); + when(mockContext.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(mockShortcutManager); + plugin.onAttachedToActivity(mockActivityPluginBinding); // Act plugin.onAttachedToActivity(mockActivityPluginBinding); @@ -123,6 +130,15 @@ public void onNewIntent_buildVersionSupported_invokesLaunchMethod() setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin); setBuildVersion(SUPPORTED_BUILD); 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); + final Context mockContext = mock(Context.class); + when(mockMainActivity.getApplicationContext()).thenReturn(mockContext); + final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); + when(mockContext.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(mockShortcutManager); + plugin.onAttachedToActivity(mockActivityPluginBinding); // Act final boolean onNewIntentReturn = plugin.onNewIntent(mockIntent); From a962152c274ea5d275cd877f8c75f0a037e041eb Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 7 Jun 2022 13:44:43 -0400 Subject: [PATCH 14/20] Fix CHANGELOG style --- packages/quick_actions/quick_actions_android/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index ccb48acf0b9d..698e5a915b4c 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.6.1 -* Allow Android to trigger quick actions without restarting the app. +* Allows Android to trigger quick actions without restarting the app. ## 0.6.0+9 From f06f961ab90fa612647c5bd36a5e73ef5da87404 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 8 Jun 2022 19:23:16 +0800 Subject: [PATCH 15/20] Use shortcut id to locate actual shortcut `getDynamicShortcuts` does not seem to return shortcuts in the same order as `expectedShortcuts`. To prevent this situation, we use the shortcut id to locate since it is unique. --- .../plugins/quickactionsexample/QuickActionsTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 48650d0ede1b..c693b739760d 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -75,9 +75,13 @@ public void appShortcutsAreCreated() { assertEquals(expectedShortcuts.size(), dynamicShortcuts.size()); for (int i = 0; i < expectedShortcuts.size(); i++) { ShortcutInfo expectedShortcut = expectedShortcuts.get(i); - ShortcutInfo dynamicShortcut = dynamicShortcuts.get(i); + ShortcutInfo dynamicShortcut = + dynamicShortcuts + .stream() + .filter(s -> s.getId().equals(expectedShortcut.getId())) + .findFirst() + .get(); - assertEquals(expectedShortcut.getId(), dynamicShortcut.getId()); assertEquals(expectedShortcut.getShortLabel(), dynamicShortcut.getShortLabel()); assertEquals(expectedShortcut.getLongLabel(), dynamicShortcut.getLongLabel()); } From a3437e1a2d1d87108f44ccbee462318421853857 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Sun, 12 Jun 2022 11:39:37 +0800 Subject: [PATCH 16/20] Wait for all the shortcut creations before running test --- .../plugins/quickactionsexample/QuickActionsTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index c693b739760d..6251f9fd9d08 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -23,6 +23,7 @@ import androidx.test.uiautomator.UiObjectNotFoundException; import androidx.test.uiautomator.UiScrollable; import androidx.test.uiautomator.UiSelector; +import androidx.test.uiautomator.Until; import io.flutter.plugins.quickactions.QuickActionsPlugin; import java.util.ArrayList; import java.util.List; @@ -44,6 +45,7 @@ public void setUp() { context = ApplicationProvider.getApplicationContext(); device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); scenario = ensureAppRunToView(); + ensureAllAppShortcutsAreCreated(); } @After @@ -73,8 +75,7 @@ public void appShortcutsAreCreated() { // Assert the app shortcuts defined in ../lib/main.dart. assertFalse(dynamicShortcuts.isEmpty()); assertEquals(expectedShortcuts.size(), dynamicShortcuts.size()); - for (int i = 0; i < expectedShortcuts.size(); i++) { - ShortcutInfo expectedShortcut = expectedShortcuts.get(i); + for (ShortcutInfo expectedShortcut : expectedShortcuts) { ShortcutInfo dynamicShortcut = dynamicShortcuts .stream() @@ -129,6 +130,10 @@ public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundExce Assert.assertEquals(initialActivity.get(), currentActivity.get()); } + private void ensureAllAppShortcutsAreCreated() { + device.wait(Until.hasObject(By.text("actions ready")), 1000); + } + private List createMockShortcuts() { List expectedShortcuts = new ArrayList<>(); From c359441e32053399be558a3b2818a14b7287ba9f Mon Sep 17 00:00:00 2001 From: TabooSun Date: Fri, 17 Jun 2022 23:16:36 +0800 Subject: [PATCH 17/20] Change the integration_test to run the app --- .../integration_test/quick_actions_test.dart | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/example/integration_test/quick_actions_test.dart b/packages/quick_actions/quick_actions_android/example/integration_test/quick_actions_test.dart index f9c42ad109e7..e0abe90f75aa 100644 --- a/packages/quick_actions/quick_actions_android/example/integration_test/quick_actions_test.dart +++ b/packages/quick_actions/quick_actions_android/example/integration_test/quick_actions_test.dart @@ -2,23 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; +import 'package:quick_actions_example/main.dart' as app; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('Can set shortcuts', (WidgetTester tester) async { - final QuickActionsPlatform quickActions = QuickActionsPlatform.instance; - await quickActions.initialize((String value) {}); + testWidgets('Can run MyApp', (WidgetTester tester) async { + app.main(); - const ShortcutItem shortCutItem = ShortcutItem( - type: 'action_one', - localizedTitle: 'Action one', - icon: 'AppIcon', - ); - expect( - quickActions.setShortcutItems([shortCutItem]), completes); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.byType(Text), findsWidgets); + expect(find.byType(app.MyHomePage), findsOneWidget); }); } From a1a4546a4fc02e9bee3116182f93cba9de8c2b16 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 22 Jun 2022 20:47:21 +0800 Subject: [PATCH 18/20] Await the description in home page before asserting --- .../plugins/quickactionsexample/QuickActionsTest.java | 9 ++++++++- .../quick_actions_android/example/lib/main.dart | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 6251f9fd9d08..299fcfcc59f3 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -104,6 +104,10 @@ public void appShortcutExistsAfterLongPressingAppIcon() throws UiObjectNotFoundE @Test public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundException { + Log.i( + QuickActionsTest.class.getSimpleName(), + "Start running appShortcutLaunchActivityAfterPressing test"); + // Arrange List shortcuts = createMockShortcuts(); String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); @@ -116,6 +120,7 @@ public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundExce UiObject appShortcut = device.findObject(new UiSelector().text(firstShortcut.getShortLabel().toString())); appShortcut.clickAndWaitForNewWindow(); + device.wait(Until.hasObject(By.descContains("On home screen")), 1000); AtomicReference currentActivity = new AtomicReference<>(); scenario.onActivity(currentActivity::set); @@ -162,7 +167,9 @@ private ActivityScenario ensureAppRunToView() { } private UiObject findAppIcon(UiDevice device, String appName) throws UiObjectNotFoundException { - device.pressHome(); + Log.i(QuickActionsTest.class.getSimpleName(), "Find app icon, pressing home..."); + boolean pressHomeResult = device.pressHome(); + Log.i(QuickActionsTest.class.getSimpleName(), "Press home result: " + pressHomeResult); // Swipe up to open App Drawer UiScrollable homeView = new UiScrollable(new UiSelector().scrollable(true)); diff --git a/packages/quick_actions/quick_actions_android/example/lib/main.dart b/packages/quick_actions/quick_actions_android/example/lib/main.dart index d8b7832bf9dc..c019335121e5 100644 --- a/packages/quick_actions/quick_actions_android/example/lib/main.dart +++ b/packages/quick_actions/quick_actions_android/example/lib/main.dart @@ -75,6 +75,7 @@ class _MyHomePageState extends State { title: Text(shortcut), ), body: const Center( + // Remember to update android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java appShortcutLaunchActivityAfterPressing if you change this. child: Text('On home screen, long press the app icon to ' 'get Action one or Action two options. Tapping on that action should ' 'set the toolbar title.'), From 116386b9419734af53f57fcffa03e0fea84a5b00 Mon Sep 17 00:00:00 2001 From: TabooSun Date: Wed, 6 Jul 2022 20:37:07 +0800 Subject: [PATCH 19/20] Fix flakey test --- .../quickactions/MethodCallHandlerImpl.java | 3 +- .../quickactionsexample/QuickActionsTest.java | 73 +++++-------------- .../example/lib/main.dart | 3 +- 3 files changed, 21 insertions(+), 58 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java index 3a58143f12cf..96b141fb9c31 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java @@ -163,7 +163,8 @@ private Intent getIntentToOpenMainActivity(String type) { .getLaunchIntentForPackage(packageName) .setAction(Intent.ACTION_RUN) .putExtra(EXTRA_ACTION, type) - .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } private static class UiThreadExecutor implements Executor { diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 299fcfcc59f3..26ebd50aaa5b 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -1,4 +1,4 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. +/// 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. @@ -9,6 +9,7 @@ import static org.junit.Assert.assertTrue; import android.content.Context; +import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.util.Log; @@ -19,10 +20,6 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.By; import androidx.test.uiautomator.UiDevice; -import androidx.test.uiautomator.UiObject; -import androidx.test.uiautomator.UiObjectNotFoundException; -import androidx.test.uiautomator.UiScrollable; -import androidx.test.uiautomator.UiSelector; import androidx.test.uiautomator.Until; import io.flutter.plugins.quickactions.QuickActionsPlugin; import java.util.ArrayList; @@ -89,38 +86,27 @@ public void appShortcutsAreCreated() { } @Test - public void appShortcutExistsAfterLongPressingAppIcon() throws UiObjectNotFoundException { - List shortcuts = createMockShortcuts(); - String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); - - findAppIcon(device, appName).longClick(); - - for (ShortcutInfo shortcut : shortcuts) { - Assert.assertTrue( - "The specified shortcut label '" + shortcut.getShortLabel() + "' does not exist.", - device.hasObject(By.text(shortcut.getShortLabel().toString()))); - } - } - - @Test - public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundException { - Log.i( - QuickActionsTest.class.getSimpleName(), - "Start running appShortcutLaunchActivityAfterPressing test"); - + public void appShortcutLaunchActivityAfterStarting() { // Arrange List shortcuts = createMockShortcuts(); - String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); ShortcutInfo firstShortcut = shortcuts.get(0); + ShortcutManager shortcutManager = + (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); + List dynamicShortcuts = shortcutManager.getDynamicShortcuts(); + ShortcutInfo dynamicShortcut = + dynamicShortcuts + .stream() + .filter(s -> s.getId().equals(firstShortcut.getId())) + .findFirst() + .get(); + Intent dynamicShortcutIntent = dynamicShortcut.getIntent(); AtomicReference initialActivity = new AtomicReference<>(); scenario.onActivity(initialActivity::set); + String appReadySentinel = " has launched"; // Act - findAppIcon(device, appName).longClick(); - UiObject appShortcut = - device.findObject(new UiSelector().text(firstShortcut.getShortLabel().toString())); - appShortcut.clickAndWaitForNewWindow(); - device.wait(Until.hasObject(By.descContains("On home screen")), 1000); + context.startActivity(dynamicShortcutIntent); + device.wait(Until.hasObject(By.descContains(appReadySentinel)), 2000); AtomicReference currentActivity = new AtomicReference<>(); scenario.onActivity(currentActivity::set); @@ -129,7 +115,7 @@ public void appShortcutLaunchActivityAfterPressing() throws UiObjectNotFoundExce "AppShortcut:" + firstShortcut.getId() + " does not launch the correct activity", // We can only find the shortcut type in content description while inspecting it in Ui // Automator Viewer. - device.hasObject(By.desc(firstShortcut.getId()))); + device.hasObject(By.desc(firstShortcut.getId() + appReadySentinel))); // This is Android SingleTop behavior in which Android does not destroy the initial activity and // launch a new activity. Assert.assertEquals(initialActivity.get(), currentActivity.get()); @@ -165,29 +151,4 @@ private ActivityScenario ensureAppRunToView() { scenario.moveToState(Lifecycle.State.STARTED); return scenario; } - - private UiObject findAppIcon(UiDevice device, String appName) throws UiObjectNotFoundException { - Log.i(QuickActionsTest.class.getSimpleName(), "Find app icon, pressing home..."); - boolean pressHomeResult = device.pressHome(); - Log.i(QuickActionsTest.class.getSimpleName(), "Press home result: " + pressHomeResult); - - // Swipe up to open App Drawer - UiScrollable homeView = new UiScrollable(new UiSelector().scrollable(true)); - homeView.scrollForward(); - - if (!device.hasObject(By.text(appName))) { - Log.i( - QuickActionsTest.class.getSimpleName(), - "Attempting to scroll App Drawer for App Icon..."); - UiScrollable appDrawer = new UiScrollable(new UiSelector().scrollable(true)); - // The scrollTextIntoView scrolls to the beginning before performing searching scroll; this - // causes an issue in a scenario where the view is already in the beginning. In this case, it - // scrolls back to home view. Therefore, we perform a dummy forward scroll to ensure it is not - // in the beginning. - appDrawer.scrollForward(); - appDrawer.scrollTextIntoView(appName); - } - - return device.findObject(new UiSelector().text(appName)); - } } diff --git a/packages/quick_actions/quick_actions_android/example/lib/main.dart b/packages/quick_actions/quick_actions_android/example/lib/main.dart index c019335121e5..0b044e0d1622 100644 --- a/packages/quick_actions/quick_actions_android/example/lib/main.dart +++ b/packages/quick_actions/quick_actions_android/example/lib/main.dart @@ -42,9 +42,10 @@ class _MyHomePageState extends State { final QuickActionsAndroid quickActions = QuickActionsAndroid(); quickActions.initialize((String shortcutType) { + print('Handling shortcut: $shortcutType'); setState(() { if (shortcutType != null) { - shortcut = shortcutType; + shortcut = '$shortcutType has launched'; } }); }); From 33bcb2404afc87ca9d7c60e759518a0e4c042d0c Mon Sep 17 00:00:00 2001 From: TabooSun Date: Sat, 9 Jul 2022 07:46:08 +0800 Subject: [PATCH 20/20] Fix license comment --- .../flutter/plugins/quickactionsexample/QuickActionsTest.java | 2 +- .../quick_actions/quick_actions_android/example/lib/main.dart | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java index 26ebd50aaa5b..8b50fd7a90eb 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java @@ -1,4 +1,4 @@ -/// Copyright 2013 The Flutter Authors. All rights reserved. +// 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. diff --git a/packages/quick_actions/quick_actions_android/example/lib/main.dart b/packages/quick_actions/quick_actions_android/example/lib/main.dart index 0b044e0d1622..8f66e69ffb4e 100644 --- a/packages/quick_actions/quick_actions_android/example/lib/main.dart +++ b/packages/quick_actions/quick_actions_android/example/lib/main.dart @@ -42,7 +42,6 @@ class _MyHomePageState extends State { final QuickActionsAndroid quickActions = QuickActionsAndroid(); quickActions.initialize((String shortcutType) { - print('Handling shortcut: $shortcutType'); setState(() { if (shortcutType != null) { shortcut = '$shortcutType has launched'; @@ -76,7 +75,6 @@ class _MyHomePageState extends State { title: Text(shortcut), ), body: const Center( - // Remember to update android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java appShortcutLaunchActivityAfterPressing if you change this. child: Text('On home screen, long press the app icon to ' 'get Action one or Action two options. Tapping on that action should ' 'set the toolbar title.'),