-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[quick_actions] Android handle quick action without restart #5048
Changes from 7 commits
f4d657f
42ca2da
d709a8e
d00c370
b61dc9c
661bc5d
28a82b3
8c754e3
84372e8
4c83cd4
ad7b4a9
24bdc10
4784fae
b2cff95
a962152
a623044
f06f961
36bc6ea
a3437e1
ac3dd42
c359441
d03b75c
a1a4546
87db162
116386b
791fcfb
de02c2b
33bcb24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way Dart versioning works for pre-1.0, this is a breaking version change (see link in the PR checklist about Dart versions). Is there anything about this change that actually breaks clients? |
||
|
||
environment: | ||
sdk: ">=2.14.0 <3.0.0" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
stuartmorgan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
xmlns:tools="http://schemas.android.com/tools" | ||
package="io.flutter.plugins.quickactions"> | ||
xmlns:tools="http://schemas.android.com/tools" | ||
package="io.flutter.plugins.quickactions"> | ||
|
||
<uses-sdk tools:overrideLibrary="android_libs.ub_uiautomator"/> | ||
</manifest> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are calling Java code from Java code via the method channel? And why are you calling a getter but not using the result? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have updated the code. |
||
channel.invokeMethod("launch", intent.getStringExtra(MethodCallHandlerImpl.EXTRA_ACTION)); | ||
} | ||
return false; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,169 @@ | ||
// 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please revert this change; it's why the format check is failing. The licenses follow an exact format, whitespace included. |
||
|
||
package io.flutter.plugins.quickactionsexample; | ||
|
||
import static org.junit.Assert.assertTrue; | ||
import static org.junit.Assert.*; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This violates style: https://google.github.io/styleguide/javaguide.html#s3.3.1-wildcard-imports There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
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.*; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
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<QuickActionsTestActivity> 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() { | ||
public void quickActionPluginIsAdded() { | ||
camsim99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
final ActivityScenario<QuickActionsTestActivity> scenario = | ||
ActivityScenario.launch(QuickActionsTestActivity.class); | ||
scenario.onActivity( | ||
activity -> { | ||
assertTrue(activity.engine.getPlugins().has(QuickActionsPlugin.class)); | ||
}); | ||
} | ||
|
||
@Test | ||
public void appShortcutsAreCreated() { | ||
List<ShortcutInfo> expectedShortcuts = createMockShortcuts(); | ||
|
||
ShortcutManager shortcutManager = | ||
(ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); | ||
List<ShortcutInfo> dynamicShortcuts = shortcutManager.getDynamicShortcuts(); | ||
|
||
// 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); | ||
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<ShortcutInfo> 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 { | ||
// Arrange | ||
List<ShortcutInfo> shortcuts = createMockShortcuts(); | ||
String appName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); | ||
ShortcutInfo firstShortcut = shortcuts.get(0); | ||
AtomicReference<QuickActionsTestActivity> initialActivity = new AtomicReference<>(); | ||
scenario.onActivity(initialActivity::set); | ||
|
||
// Act | ||
findAppIcon(device, appName).longClick(); | ||
UiObject appShortcut = | ||
device.findObject(new UiSelector().text(firstShortcut.getShortLabel().toString())); | ||
appShortcut.clickAndWaitForNewWindow(); | ||
AtomicReference<QuickActionsTestActivity> currentActivity = new AtomicReference<>(); | ||
scenario.onActivity(currentActivity::set); | ||
|
||
// Assert | ||
Assert.assertTrue( | ||
"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()))); | ||
// 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<ShortcutInfo> createMockShortcuts() { | ||
List<ShortcutInfo> expectedShortcuts = new ArrayList<>(); | ||
|
||
String actionOneLocalizedTitle = "Action one"; | ||
expectedShortcuts.add( | ||
new ShortcutInfo.Builder(context, "action_one") | ||
.setShortLabel(actionOneLocalizedTitle) | ||
.setLongLabel(actionOneLocalizedTitle) | ||
.build()); | ||
|
||
String actionTwoLocalizedTitle = "Action two"; | ||
expectedShortcuts.add( | ||
new ShortcutInfo.Builder(context, "action_two") | ||
.setShortLabel(actionTwoLocalizedTitle) | ||
.setLongLabel(actionTwoLocalizedTitle) | ||
.build()); | ||
|
||
return expectedShortcuts; | ||
} | ||
|
||
private ActivityScenario<QuickActionsTestActivity> ensureAppRunToView() { | ||
final ActivityScenario<QuickActionsTestActivity> 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)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stuartmorgan Should this include the
NEXT
item since this is a version bump?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but the larger problem is that this version bump is to the wrong package. The changes are to
quick_actions_android
, notquick_actions
, so that's what needs CHANGELOG and version updates.@TabooSun Please make sure the
publishable
CI check is passing when you update your PR; the failure messages there explain the problem.