diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index a25ee3c813618..35370f0998fdf 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -709,7 +709,7 @@ public final class ShortcutInfo implements Parcelable { @NonNull @Deprecated public Builder setId(@NonNull String id) { - mId = Preconditions.checkStringNotEmpty(id, "id"); + mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); return this; } @@ -721,14 +721,17 @@ public final class ShortcutInfo implements Parcelable { */ public Builder(Context context, String id) { mContext = context; - mId = Preconditions.checkStringNotEmpty(id, "id"); + mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); } /** * Sets the target activity. A shortcut will be shown with this activity on the launcher. * - *
This is a mandatory field, unless it's passed to - * {@link ShortcutManager#updateShortcuts(List)}. + *
Only "main" activities -- i.e. ones with an intent filter for + * {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER} can be target activities. + * + *
By default, the first main activity defined in the application manifest will be + * the target. * *
The package name of the target activity must match the package name of the shortcut
* publisher.
@@ -738,7 +741,7 @@ public final class ShortcutInfo implements Parcelable {
*/
@NonNull
public Builder setActivity(@NonNull ComponentName activity) {
- mActivity = Preconditions.checkNotNull(activity, "activity");
+ mActivity = Preconditions.checkNotNull(activity, "activity cannot be null");
return this;
}
@@ -785,7 +788,7 @@ public final class ShortcutInfo implements Parcelable {
@NonNull
public Builder setShortLabel(@NonNull CharSequence shortLabel) {
Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
- mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel");
+ mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty");
return this;
}
@@ -810,7 +813,7 @@ public final class ShortcutInfo implements Parcelable {
@NonNull
public Builder setLongLabel(@NonNull CharSequence longLabel) {
Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
- mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel");
+ mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty");
return this;
}
@@ -854,7 +857,8 @@ public final class ShortcutInfo implements Parcelable {
Preconditions.checkState(
mDisabledMessageResId == 0, "disabledMessageResId already set");
mDisabledMessage =
- Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage");
+ Preconditions.checkStringNotEmpty(disabledMessage,
+ "disabledMessage cannot be empty");
return this;
}
@@ -876,8 +880,8 @@ public final class ShortcutInfo implements Parcelable {
*/
@NonNull
public Builder setIntent(@NonNull Intent intent) {
- mIntent = Preconditions.checkNotNull(intent, "intent");
- Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set");
+ mIntent = Preconditions.checkNotNull(intent, "intent cannot be null");
+ Preconditions.checkNotNull(mIntent.getAction(), "intent's action must be set");
return this;
}
@@ -944,6 +948,11 @@ public final class ShortcutInfo implements Parcelable {
return mActivity;
}
+ /** @hide */
+ public void setActivity(ComponentName activity) {
+ mActivity = activity;
+ }
+
/**
* Icon.
*
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index d637586c8def7..67e4e93fdbdab 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -54,8 +54,6 @@ import java.util.function.Predicate;
* User information used by {@link ShortcutService}.
*
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
- *
- * TODO Max dynamic shortcuts cap should be per activity.
*/
class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -321,9 +319,27 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* Remove a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
* is pinned, it'll remain as a pinned shortcut, and is still enabled.
+ *
+ * @return true if it's actually removed because it wasn't pinned, or false if it's still
+ * pinned.
*/
- public void deleteDynamicWithId(@NonNull String shortcutId) {
- deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+ public boolean deleteDynamicWithId(@NonNull String shortcutId) {
+ final ShortcutInfo removed = deleteOrDisableWithId(
+ shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+ return removed == null;
+ }
+
+ /**
+ * Disable a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
+ * is pinned, it'll remain as a pinned shortcut, but will be disabled.
+ *
+ * @return true if it's actually removed because it wasn't pinned, or false if it's still
+ * pinned.
+ */
+ private boolean disableDynamicWithId(@NonNull String shortcutId) {
+ final ShortcutInfo disabled = deleteOrDisableWithId(
+ shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false);
+ return disabled == null;
}
/**
@@ -599,14 +615,14 @@ class ShortcutPackage extends ShortcutPackageItem {
*
* @return TRUE if any shortcuts have been changed.
*/
- public boolean handlePackageAddedOrUpdated(boolean isNewApp) {
+ public boolean handlePackageAddedOrUpdated(boolean isNewApp, boolean forceRescan) {
final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
getPackageName(), getPackageUserId());
if (pi == null) {
return false; // Shouldn't happen.
}
- if (!isNewApp) {
+ if (!isNewApp && !forceRescan) {
// Make sure the version code or last update time has changed.
// Otherwise, nothing to do.
if (getPackageInfo().getVersionCode() >= pi.versionCode
@@ -649,12 +665,26 @@ class ShortcutPackage extends ShortcutPackageItem {
boolean changed = false;
// For existing shortcuts, update timestamps if they have any resources.
+ // Also check if shortcuts' activities are still main activities. Otherwise, disable them.
if (!isNewApp) {
Resources publisherRes = null;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.isDynamic()) {
+ if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) {
+ Slog.w(TAG, String.format(
+ "%s is no longer main activity. Disabling shorcut %s.",
+ getPackageName(), si.getId()));
+ if (disableDynamicWithId(si.getId())) {
+ continue; // Actually removed.
+ }
+ // Still pinned, so fall-through and possibly update the resources.
+ }
+ changed = true;
+ }
+
if (si.hasAnyResources()) {
if (!si.isOriginallyFromManifest()) {
if (publisherRes == null) {
@@ -912,9 +942,8 @@ class ShortcutPackage extends ShortcutPackageItem {
final ComponentName newActivity = newShortcut.getActivity();
if (newActivity == null) {
if (operation != ShortcutService.OPERATION_UPDATE) {
- // This method may be called before validating shortcuts, so this may happen,
- // and is a caller side error.
- throw new NullPointerException("Activity must be provided");
+ service.wtf("Activity must not be null at this point");
+ continue; // Just ignore this invalid case.
}
continue; // Activity can be null for update.
}
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index c349b758b0cee..858e1cd9284b4 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -21,6 +21,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -58,18 +59,36 @@ public class ShortcutParser {
@Nullable
public static List shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("s1", "s2", "s3")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// From different package.
- reset(c0);
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_2),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
.haveIds("s1", "s2", "s3")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Different user, callback shouldn't be called.
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- waitOnMainThread();
- verify(c0, times(0)).onShortcutsChanged(
- anyString(),
- any(List.class),
- any(UserHandle.class)
- );
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ }).assertNoCallbackCalled();
+
// Test for addDynamicShortcuts.
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- dumpsysOnLogcat("before addDynamicShortcuts");
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
+ });
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("s1", "s2", "s3", "s4")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Test for remove
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- mManager.removeDynamicShortcuts(list("s1"));
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.removeDynamicShortcuts(list("s1"));
+ });
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("s2", "s3", "s4")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Test for update
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- assertTrue(mManager.updateShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"))));
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.updateShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"))));
+ });
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
+ // All remaining shortcuts will be passed regardless of what's been updated.
.haveIds("s2", "s3", "s4")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Test for deleteAll
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- mManager.removeAllDynamicShortcuts();
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.removeAllDynamicShortcuts();
+ });
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.isEmpty();
// Update package1 with manifest shortcuts
- reset(c0);
- addManifestShortcutResource(
- new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
- R.xml.shortcut_2);
- updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.areAllManifest()
.areAllWithKeyFieldsOnly()
.haveIds("ms1", "ms2");
@@ -2518,58 +2581,42 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
- reset(c0); // Check the callback for the next API call.
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- mManager.removeDynamicShortcuts(list("s2"));
+ assertForLauncherCallback(mLauncherApps, () -> {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.removeDynamicShortcuts(list("s2"));
- assertWith(getCallerShortcuts())
- .haveIds("ms2", "s1", "s2")
+ assertWith(getCallerShortcuts())
+ .haveIds("ms2", "s1", "s2")
- .selectByIds("ms2")
- .areAllNotManifest()
- .areAllPinned()
- .areAllImmutable()
- .areAllDisabled()
+ .selectByIds("ms2")
+ .areAllNotManifest()
+ .areAllPinned()
+ .areAllImmutable()
+ .areAllDisabled()
- .revertToOriginalList()
- .selectByIds("s1")
- .areAllDynamic()
- .areAllNotPinned()
- .areAllEnabled()
+ .revertToOriginalList()
+ .selectByIds("s1")
+ .areAllDynamic()
+ .areAllNotPinned()
+ .areAllEnabled()
- .revertToOriginalList()
- .selectByIds("s2")
- .areAllNotDynamic()
- .areAllPinned()
- .areAllEnabled()
- ;
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ .revertToOriginalList()
+ .selectByIds("s2")
+ .areAllNotDynamic()
+ .areAllPinned()
+ .areAllEnabled()
+ ;
+ });
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("ms2", "s1", "s2")
.areAllWithKeyFieldsOnly();
// Remove CALLING_PACKAGE_2
- reset(c0);
- uninstallPackage(USER_0, CALLING_PACKAGE_2);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
-
- // Should get a callback with an empty list.
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_2),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertWith(shortcuts.getValue())
+ assertForLauncherCallback(mLauncherApps, () -> {
+ uninstallPackage(USER_0, CALLING_PACKAGE_2);
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0,
+ /* appStillExists = */ false);
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
.isEmpty();
}
@@ -2970,7 +3017,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Nonexistent package.
uninstallPackage(USER_0, "abc");
- mService.cleanUpPackageLocked("abc", USER_0, USER_0);
+ mService.cleanUpPackageLocked("abc", USER_0, USER_0, /* appStillExists = */ false);
// No changes.
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
@@ -3002,7 +3049,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove a package.
uninstallPackage(USER_0, CALLING_PACKAGE_1);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0,
+ /* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3033,7 +3081,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove a launcher.
uninstallPackage(USER_10, LAUNCHER_1);
- mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
+ mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10, /* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3061,7 +3109,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove a package.
uninstallPackage(USER_10, CALLING_PACKAGE_2);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10,
+ /* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3089,7 +3138,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove the other launcher from user 10 too.
uninstallPackage(USER_10, LAUNCHER_2);
- mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
+ mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10,
+ /* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3117,7 +3167,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// More remove.
uninstallPackage(USER_10, CALLING_PACKAGE_1);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10,
+ /* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3143,6 +3194,74 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mService.saveDirtyInfo();
}
+ public void testCleanupPackage_republishManifests() {
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s2", "s3", "ms1", "ms2"), HANDLE_USER_0);
+ });
+
+ // Remove ms2 from manifest.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"))));
+
+ // Make sure the shortcuts are in the intended state.
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "s1", "s2", "s3")
+
+ .selectByIds("ms1")
+ .areAllManifest()
+ .areAllPinned()
+
+ .revertToOriginalList()
+ .selectByIds("ms2")
+ .areAllNotManifest()
+ .areAllPinned()
+
+ .revertToOriginalList()
+ .selectByIds("s1")
+ .areAllDynamic()
+ .areAllNotPinned()
+
+ .revertToOriginalList()
+ .selectByIds("s2")
+ .areAllDynamic()
+ .areAllPinned()
+
+ .revertToOriginalList()
+ .selectByIds("s3")
+ .areAllNotDynamic()
+ .areAllPinned();
+ });
+
+ // Clean up + re-publish manifests.
+ mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0,
+ /* appStillExists = */ true);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1")
+ .areAllManifest();
+ });
+ }
+
public void testHandleGonePackage_crossProfile() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3454,6 +3573,20 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertTrue(mManager.addDynamicShortcuts(list(
makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
)));
+ // Also add a manifest shortcut, which should be removed too.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("s1", "s2", "ms1")
+
+ .selectManifest()
+ .haveIds("ms1");
+ });
setCaller(CALLING_PACKAGE_2, USER_0);
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
@@ -3629,8 +3762,47 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
}
- public void testHandlePackageUpdate() throws Throwable {
+ public void testHandlePackageClearData_manifestRepublished() {
+ // Add two manifests and two dynamics.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.addDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"))));
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "s2"), HANDLE_USER_10);
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllEnabled()
+
+ .selectPinned()
+ .haveIds("ms2", "s2");
+ });
+
+ // Clear data
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageDataClear(CALLING_PACKAGE_1, USER_10));
+
+ // Only manifest shortcuts will remain, and are no longer pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllNotPinned();
+ });
+ }
+
+ public void testHandlePackageUpdate() throws Throwable {
// Set up shortcuts and launchers.
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
@@ -3894,6 +4066,202 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
+ public void testHandlePackageChanged() {
+ final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1");
+ final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2");
+
+ addManifestShortcutResource(ACTIVITY1, R.xml.shortcut_1);
+ addManifestShortcutResource(ACTIVITY2, R.xml.shortcut_1_alt);
+
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.addDynamicShortcuts(list(
+ makeShortcutWithActivity("s1", ACTIVITY1),
+ makeShortcutWithActivity("s2", ACTIVITY2)
+ )));
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1-alt", "s2"), HANDLE_USER_10);
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms1-alt", "s1", "s2")
+ .areAllEnabled()
+
+ .selectPinned()
+ .haveIds("ms1-alt", "s2")
+
+ .revertToOriginalList()
+ .selectByIds("ms1", "s1")
+ .areAllWithActivity(ACTIVITY1)
+
+ .revertToOriginalList()
+ .selectByIds("ms1-alt", "s2")
+ .areAllWithActivity(ACTIVITY2)
+ ;
+ });
+
+ // First, no changes.
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms1-alt", "s1", "s2")
+ .areAllEnabled()
+
+ .selectPinned()
+ .haveIds("ms1-alt", "s2")
+
+ .revertToOriginalList()
+ .selectByIds("ms1", "s1")
+ .areAllWithActivity(ACTIVITY1)
+
+ .revertToOriginalList()
+ .selectByIds("ms1-alt", "s2")
+ .areAllWithActivity(ACTIVITY2)
+ ;
+ });
+
+ // Disable activity 1
+ mEnabledActivityChecker = (activity, userId) -> !ACTIVITY1.equals(activity);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1-alt", "s2")
+ .areAllEnabled()
+
+ .selectPinned()
+ .haveIds("ms1-alt", "s2")
+
+ .revertToOriginalList()
+ .selectByIds("ms1-alt", "s2")
+ .areAllWithActivity(ACTIVITY2)
+ ;
+ });
+
+ // Re-enable activity 1.
+ // Manifest shortcuts will be re-published, but dynamic ones are not.
+ mEnabledActivityChecker = (activity, userId) -> true;
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms1-alt", "s2")
+ .areAllEnabled()
+
+ .selectPinned()
+ .haveIds("ms1-alt", "s2")
+
+ .revertToOriginalList()
+ .selectByIds("ms1")
+ .areAllWithActivity(ACTIVITY1)
+
+ .revertToOriginalList()
+ .selectByIds("ms1-alt", "s2")
+ .areAllWithActivity(ACTIVITY2)
+ ;
+ });
+
+ // Disable activity 2
+ // Because "ms1-alt" and "s2" are both pinned, they will remain, but disabled.
+ mEnabledActivityChecker = (activity, userId) -> !ACTIVITY2.equals(activity);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms1-alt", "s2")
+
+ .selectDynamic().isEmpty().revertToOriginalList() // no dynamics.
+
+ .selectPinned()
+ .haveIds("ms1-alt", "s2")
+ .areAllDisabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms1")
+ .areAllWithActivity(ACTIVITY1)
+ .areAllEnabled()
+ ;
+ });
+ }
+
+ public void testHandlePackageUpdate_activityNoLongerMain() throws Throwable {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcutWithActivity("s1a",
+ new ComponentName(getCallingPackage(), "act1")),
+ makeShortcutWithActivity("s1b",
+ new ComponentName(getCallingPackage(), "act1")),
+ makeShortcutWithActivity("s2a",
+ new ComponentName(getCallingPackage(), "act2")),
+ makeShortcutWithActivity("s2b",
+ new ComponentName(getCallingPackage(), "act2")),
+ makeShortcutWithActivity("s3a",
+ new ComponentName(getCallingPackage(), "act3")),
+ makeShortcutWithActivity("s3b",
+ new ComponentName(getCallingPackage(), "act3"))
+ )));
+ assertWith(getCallerShortcuts())
+ .haveIds("s1a", "s1b", "s2a", "s2b", "s3a", "s3b")
+ .areAllDynamic();
+ });
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s1b", "s2b", "s3b"),
+ HANDLE_USER_0);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("s1a", "s1b", "s2a", "s2b", "s3a", "s3b")
+ .areAllDynamic()
+
+ .selectByIds("s1b", "s2b", "s3b")
+ .areAllPinned();
+ });
+
+ // Update the app and act2 and act3 are no longer main.
+ mMainActivityChecker = (activity, userId) -> {
+ return activity.getClassName().equals("act1");
+ };
+
+ setCaller(LAUNCHER_1, USER_0);
+ assertForLauncherCallback(mLauncherApps, () -> {
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
+ }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
+ // Make sure the launcher gets callbacks.
+ .haveIds("s1a", "s1b", "s2b", "s3b")
+ .areAllWithKeyFieldsOnly();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ // s2a and s3a are gone, but s2b and s3b will remain because they're pinned, and
+ // disabled.
+ assertWith(getCallerShortcuts())
+ .haveIds("s1a", "s1b", "s2b", "s3b")
+
+ .selectByIds("s1a", "s1b")
+ .areAllDynamic()
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("s2b", "s3b")
+ .areAllNotDynamic()
+ .areAllDisabled()
+ .areAllPinned()
+ ;
+ });
+ }
+
protected void prepareForBackupTest() {
prepareCrossProfileDataSet();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 2856866bcbe75..f570ff24ce361 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -72,18 +72,68 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
"ID must be provided",
() -> new ShortcutInfo.Builder(getTestContext()).build());
- assertExpectException(NullPointerException.class, "Intent action must be set",
- () -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
-
- assertExpectException(NullPointerException.class, "Activity must be provided", () -> {
- ShortcutInfo si = new ShortcutInfo.Builder(getTestContext()).setId("id").build();
- assertTrue(getManager().setDynamicShortcuts(list(si)));
- });
+ assertExpectException(
+ RuntimeException.class,
+ "id cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), null));
+ assertExpectException(
+ RuntimeException.class,
+ "id cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), ""));
+
+ assertExpectException(
+ RuntimeException.class,
+ "intent cannot be null",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "action must be set",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
+
+ assertExpectException(
+ RuntimeException.class,
+ "activity cannot be null",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setActivity(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "shortLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "shortLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(""));
+
+ assertExpectException(
+ RuntimeException.class,
+ "longLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "longLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(""));
+
+ assertExpectException(
+ RuntimeException.class,
+ "disabledMessage cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "disabledMessage cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(""));
+
+ assertExpectException(NullPointerException.class, "action must be set",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
+
+ // same for add.
assertExpectException(
IllegalArgumentException.class, "Short label must be provided", () -> {
- ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.build();
assertTrue(getManager().setDynamicShortcuts(list(si)));
@@ -91,25 +141,24 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertExpectException(
IllegalArgumentException.class, "Short label must be provided", () -> {
- ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.build();
assertTrue(getManager().addDynamicShortcuts(list(si)));
});
+ // same for add.
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
- ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.setShortLabel("x")
.build();
assertTrue(getManager().setDynamicShortcuts(list(si)));
});
+ // same for add.
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
- ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.setShortLabel("x")
.build();
@@ -117,18 +166,17 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
assertExpectException(
- IllegalStateException.class, "package name mismatch", () -> {
- ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
+ IllegalStateException.class, "does not belong to package", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName("xxx", "s"))
.build();
assertTrue(getManager().setDynamicShortcuts(list(si)));
});
+ // same for add.
assertExpectException(
- IllegalStateException.class, "package name mismatch", () -> {
- ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
+ IllegalStateException.class, "does not belong to package", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName("xxx", "s"))
.build();
assertTrue(getManager().addDynamicShortcuts(list(si)));
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 4aa7590b491a2..7d7285a24a044 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -24,9 +24,11 @@ import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -35,11 +37,14 @@ import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.Callback;
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.BaseBundle;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -54,6 +59,7 @@ import junit.framework.AssertionFailedError;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.io.BufferedReader;
@@ -69,6 +75,7 @@ import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.concurrent.CountDownLatch;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -664,8 +671,8 @@ public class ShortcutManagerTestUtils {
}
private ShortcutListAsserter(ShortcutListAsserter original, List
shortcuts = ArgumentCaptor.forClass(List.class);
+ verify(mCallback, times(1)).onShortcutsChanged(
+ eq(publisherPackageName),
+ shortcuts.capture(),
+ eq(publisherUserHandle));
+ return new ShortcutListAsserter(shortcuts.getValue());
+ }
+ }
+
+ public static LauncherCallbackAsserter assertForLauncherCallback(
+ LauncherApps launcherApps, Runnable body) throws InterruptedException {
+ final LauncherCallbackAsserter asserter = new LauncherCallbackAsserter();
+ launcherApps.registerCallback(asserter.getMockCallback(),
+ new Handler(Looper.getMainLooper()));
+
+ body.run();
+
+ waitOnMainThread();
+
+ return asserter;
+ }
}