diff --git a/api/current.txt b/api/current.txt index 617971e8122e0..32fb1e7e8bfd4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11672,6 +11672,7 @@ package android.content.pm { method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int); method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(@Nullable java.util.List); field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4 + field public static final int FLAG_MATCH_CACHED = 16; // 0x10 field public static final int FLAG_MATCH_DYNAMIC = 1; // 0x1 field public static final int FLAG_MATCH_MANIFEST = 8; // 0x8 field public static final int FLAG_MATCH_PINNED = 2; // 0x2 @@ -12365,6 +12366,7 @@ package android.content.pm { method @Nullable public CharSequence getShortLabel(); method public android.os.UserHandle getUserHandle(); method public boolean hasKeyFieldsOnly(); + method public boolean isCached(); method public boolean isDeclaredInManifest(); method public boolean isDynamic(); method public boolean isEnabled(); @@ -12414,14 +12416,20 @@ package android.content.pm { method @NonNull public java.util.List getManifestShortcuts(); method public int getMaxShortcutCountPerActivity(); method @NonNull public java.util.List getPinnedShortcuts(); + method @NonNull public java.util.List getShortcuts(int); method public boolean isRateLimitingActive(); method public boolean isRequestPinShortcutSupported(); method public void removeAllDynamicShortcuts(); method public void removeDynamicShortcuts(@NonNull java.util.List); + method public void removeLongLivedShortcuts(@NonNull java.util.List); method public void reportShortcutUsed(String); method public boolean requestPinShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.content.IntentSender); method public boolean setDynamicShortcuts(@NonNull java.util.List); method public boolean updateShortcuts(@NonNull java.util.List); + field public static final int FLAG_MATCH_CACHED = 8; // 0x8 + field public static final int FLAG_MATCH_DYNAMIC = 2; // 0x2 + field public static final int FLAG_MATCH_MANIFEST = 1; // 0x1 + field public static final int FLAG_MATCH_PINNED = 4; // 0x4 } public class Signature implements android.os.Parcelable { diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index 276853d3b8602..747e929eda98f 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -78,4 +78,8 @@ interface IShortcutService { ParceledListSlice getShareTargets(String packageName, in IntentFilter filter, int userId); boolean hasShareTargets(String packageName, String packageToCheck, int userId); + + void removeLongLivedShortcuts(String packageName, in List shortcutIds, int userId); + + ParceledListSlice getShortcuts(String packageName, int matchFlags, int userId); } \ No newline at end of file diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index ed958b1186500..cea0b6b5f3ad5 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -324,6 +324,11 @@ public class LauncherApps { */ public static final int FLAG_MATCH_MANIFEST = 1 << 3; + /** + * Include cached shortcuts in the result. + */ + public static final int FLAG_MATCH_CACHED = 1 << 4; + /** @hide kept for unit tests */ @Deprecated public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST; @@ -345,11 +350,11 @@ public class LauncherApps { public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10; /** - * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST + * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_CACHED * @hide */ public static final int FLAG_MATCH_ALL_KINDS = - FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST; + FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_CACHED; /** * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED @@ -386,8 +391,8 @@ public class LauncherApps { FLAG_MATCH_DYNAMIC, FLAG_MATCH_PINNED, FLAG_MATCH_MANIFEST, + FLAG_MATCH_CACHED, FLAG_GET_KEY_FIELDS_ONLY, - FLAG_MATCH_MANIFEST, }) @Retention(RetentionPolicy.SOURCE) public @interface QueryFlags {} @@ -454,6 +459,7 @@ public class LauncherApps { *
  • {@link #FLAG_MATCH_DYNAMIC} *
  • {@link #FLAG_MATCH_PINNED} *
  • {@link #FLAG_MATCH_MANIFEST} + *
  • {@link #FLAG_MATCH_CACHED} *
  • {@link #FLAG_GET_KEY_FIELDS_ONLY} * */ diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index d5fb848c26921..bde4f614a39e3 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -119,6 +119,9 @@ public final class ShortcutInfo implements Parcelable { /** @hide */ public static final int FLAG_LONG_LIVED = 1 << 13; + /** @hide */ + public static final int FLAG_CACHED = 1 << 14; + /** @hide */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { FLAG_DYNAMIC, @@ -135,6 +138,7 @@ public final class ShortcutInfo implements Parcelable { FLAG_ICON_FILE_PENDING_SAVE, FLAG_SHADOW, FLAG_LONG_LIVED, + FLAG_CACHED, }) @Retention(RetentionPolicy.SOURCE) public @interface ShortcutFlags {} @@ -1681,6 +1685,16 @@ public final class ShortcutInfo implements Parcelable { addFlags(FLAG_LONG_LIVED); } + /** @hide */ + public void setCached() { + addFlags(FLAG_CACHED); + } + + /** Return whether a shortcut is cached. */ + public boolean isCached() { + return hasFlags(FLAG_CACHED); + } + /** Return whether a shortcut is dynamic. */ public boolean isDynamic() { return hasFlags(FLAG_DYNAMIC); @@ -1765,7 +1779,8 @@ public final class ShortcutInfo implements Parcelable { /** @hide */ public boolean isAlive() { - return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); + return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST) + || hasFlags(FLAG_CACHED); } /** @hide */ diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index dde8865467a17..3eea3f62fd468 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -16,6 +16,7 @@ package android.content.pm; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -40,6 +41,8 @@ import android.os.ServiceManager; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -62,6 +65,44 @@ import java.util.List; public class ShortcutManager { private static final String TAG = "ShortcutManager"; + /** + * Include manifest shortcuts in the result. + * + * @see #getShortcuts(int) + */ + public static final int FLAG_MATCH_MANIFEST = 1 << 0; + + /** + * Include dynamic shortcuts in the result. + * + * @see #getShortcuts(int) + */ + public static final int FLAG_MATCH_DYNAMIC = 1 << 1; + + /** + * Include pinned shortcuts in the result. + * + * @see #getShortcuts(int) + */ + public static final int FLAG_MATCH_PINNED = 1 << 2; + + /** + * Include cached shortcuts in the result. + * + * @see #getShortcuts(int) + */ + public static final int FLAG_MATCH_CACHED = 1 << 3; + + /** @hide */ + @IntDef(flag = true, prefix = { "FLAG_MATCH_" }, value = { + FLAG_MATCH_MANIFEST, + FLAG_MATCH_DYNAMIC, + FLAG_MATCH_PINNED, + FLAG_MATCH_CACHED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ShortcutMatchFlags {} + private final Context mContext; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final IShortcutService mService; @@ -118,8 +159,8 @@ public class ShortcutManager { @NonNull public List getDynamicShortcuts() { try { - return mService.getDynamicShortcuts(mContext.getPackageName(), injectMyUserId()) - .getList(); + return mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_DYNAMIC, + injectMyUserId()).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -137,7 +178,35 @@ public class ShortcutManager { @NonNull public List getManifestShortcuts() { try { - return mService.getManifestShortcuts(mContext.getPackageName(), injectMyUserId()) + return mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_MANIFEST, + injectMyUserId()).getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns {@link ShortcutInfo}s that match {@code matchFlags}. + * + * @param matchFlags result includes shortcuts matching this flags. Any combination of: + *
      + *
    • {@link #FLAG_MATCH_MANIFEST} + *
    • {@link #FLAG_MATCH_DYNAMIC} + *
    • {@link #FLAG_MATCH_PINNED} + *
    • {@link #FLAG_MATCH_CACHED} + *
    + + * @return list of {@link ShortcutInfo}s that match the flag. + * + *

    At least one of the {@code MATCH} flags should be set. Otherwise no shortcuts will be + * returned. + * + * @throws IllegalStateException when the user is locked. + */ + @NonNull + public List getShortcuts(@ShortcutMatchFlags int matchFlags) { + try { + return mService.getShortcuts(mContext.getPackageName(), matchFlags, injectMyUserId()) .getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -193,6 +262,20 @@ public class ShortcutManager { } } + /** + * Delete long lived shortcuts by ID. + * + * @throws IllegalStateException when the user is locked. + */ + public void removeLongLivedShortcuts(@NonNull List shortcutIds) { + try { + mService.removeLongLivedShortcuts(mContext.getPackageName(), shortcutIds, + injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Return all pinned shortcuts from the caller app. * @@ -205,8 +288,8 @@ public class ShortcutManager { @NonNull public List getPinnedShortcuts() { try { - return mService.getPinnedShortcuts(mContext.getPackageName(), injectMyUserId()) - .getList(); + return mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_PINNED, + injectMyUserId()).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 0274aee0a1681..c37ceb3bc1b0a 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -310,7 +310,7 @@ class ShortcutPackage extends ShortcutPackageItem { } /** - * Remove all shortcuts that aren't pinned nor dynamic. + * Remove all shortcuts that aren't pinned, cached nor dynamic. */ private void removeOrphans() { ArrayList removeList = null; // Lazily initialize. @@ -356,10 +356,10 @@ 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. + * is pinned or cached, it'll remain as a pinned or cached shortcut, and is still enabled. * - * @return true if it's actually removed because it wasn't pinned, or false if it's still - * pinned. + * @return true if it's removed, or false if it was not actually removed because it is either + * pinned or cached. */ public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) { final ShortcutInfo removed = deleteOrDisableWithId( @@ -383,6 +383,24 @@ class ShortcutPackage extends ShortcutPackageItem { return disabled == null; } + /** + * Remove a long lived shortcut by ID. 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 boolean deleteLongLivedWithId(@NonNull String shortcutId, boolean ignoreInvisible) { + final ShortcutInfo shortcut = mShortcuts.get(shortcutId); + if (shortcut != null) { + shortcut.clearFlags(ShortcutInfo.FLAG_CACHED); + } + final ShortcutInfo removed = deleteOrDisableWithId( + shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible, + ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + 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. @@ -419,7 +437,7 @@ class ShortcutPackage extends ShortcutPackageItem { if (!overrideImmutable) { ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true); } - if (oldShortcut.isPinned()) { + if (oldShortcut.isPinned() || oldShortcut.isCached()) { oldShortcut.setRank(0); oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); @@ -500,7 +518,7 @@ class ShortcutPackage extends ShortcutPackageItem { } }); - // Lastly, remove the ones that are no longer pinned nor dynamic. + // Lastly, remove the ones that are no longer pinned, cached nor dynamic. removeOrphans(); } @@ -1328,6 +1346,10 @@ class ShortcutPackage extends ShortcutPackageItem { // Don't adjust ranks for manifest shortcuts. continue; } + if (si.isCached() && !si.isDynamic()) { + // Don't adjust ranks for cached shortcuts that are not dynamic anymore. + continue; + } // At this point, it must be dynamic. if (!si.isDynamic()) { s.wtf("Non-dynamic shortcut found."); @@ -1869,7 +1891,7 @@ class ShortcutPackage extends ShortcutPackageItem { // Verify each shortcut's status. for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); - if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned())) { + if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned() || si.isCached())) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " is not manifest, dynamic or pinned."); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 261418ce7586c..f368666a06ba6 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -2122,6 +2122,33 @@ public class ShortcutService extends IShortcutService.Stub { verifyStates(); } + @Override + public void removeLongLivedShortcuts(String packageName, List shortcutIds, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); + + synchronized (mLock) { + throwIfUserLockedL(userId); + + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + + ps.ensureImmutableShortcutsNotIncludedWithIds((List) shortcutIds, + /*ignoreInvisible=*/ true); + + for (int i = shortcutIds.size() - 1; i >= 0; i--) { + final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); + ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); + } + + // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. + ps.adjustRanks(); + } + packageShortcutsChanged(packageName, userId); + + verifyStates(); + } + @Override public ParceledListSlice getDynamicShortcuts(String packageName, @UserIdInt int userId) { @@ -2164,6 +2191,31 @@ public class ShortcutService extends IShortcutService.Stub { } } + @Override + public ParceledListSlice getShortcuts(String packageName, + @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { + verifyCaller(packageName, userId); + + synchronized (mLock) { + throwIfUserLockedL(userId); + + final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0; + final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0; + final boolean matchManifest = (matchFlags & ShortcutManager.FLAG_MATCH_MANIFEST) != 0; + final boolean matchCached = (matchFlags & ShortcutManager.FLAG_MATCH_CACHED) != 0; + + final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0) + | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0) + | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) + | (matchCached ? ShortcutInfo.FLAG_CACHED : 0); + + return getShortcutsWithQueryLocked( + packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, + (ShortcutInfo si) -> + si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0); + } + } + @Override public ParceledListSlice getShareTargets(String packageName, IntentFilter filter, @UserIdInt int userId) { @@ -2628,6 +2680,7 @@ public class ShortcutService extends IShortcutService.Stub { final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; + final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0; final boolean canAccessAllShortcuts = canSeeAnyPinnedShortcut(callingPackage, launcherUserId, callingPid, callingUid); @@ -2659,6 +2712,9 @@ public class ShortcutService extends IShortcutService.Stub { if (matchManifest && si.isDeclaredInManifest()) { return true; } + if (matchCached && si.isCached()) { + return true; + } return false; }, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 6c917b7f86365..798420ee01370 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -82,6 +82,7 @@ import android.content.pm.LauncherApps.PinItemRequest; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; @@ -1227,6 +1228,142 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } + public void testShortcutManagerGetShortcuts_shortcutTypes() { + + // Create 3 manifest and 3 dynamic shortcuts + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_3); + 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")))); + }); + + // Pin 2 and 3 + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "ms3", "s2", "s3"), + HANDLE_USER_0); + }); + + // Cache 1 and 2 + setCaller(CALLING_PACKAGE_1); + getCallerShortcut("s1").setCached(); + getCallerShortcut("s2").setCached(); + + // Get manifest shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_MANIFEST), + "ms1", "ms2", "ms3"); + + // Get dynamic shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), + "s1", "s2", "s3"); + + // Get pinned shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED), + "ms2", "ms3", "s2", "s3"); + + // Get cached shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s1", "s2"); + + // Get manifest and dynamic shortcuts + assertShortcutIds(mManager.getShortcuts( + ShortcutManager.FLAG_MATCH_MANIFEST | ShortcutManager.FLAG_MATCH_DYNAMIC), + "ms1", "ms2", "ms3", "s1", "s2", "s3"); + + // Get manifest and pinned shortcuts + assertShortcutIds(mManager.getShortcuts( + ShortcutManager.FLAG_MATCH_MANIFEST | ShortcutManager.FLAG_MATCH_PINNED), + "ms1", "ms2", "ms3", "s2", "s3"); + + // Get manifest and cached shortcuts + assertShortcutIds(mManager.getShortcuts( + ShortcutManager.FLAG_MATCH_MANIFEST | ShortcutManager.FLAG_MATCH_CACHED), + "ms1", "ms2", "ms3", "s1", "s2"); + + // Get dynamic and pinned shortcuts + assertShortcutIds(mManager.getShortcuts( + ShortcutManager.FLAG_MATCH_DYNAMIC | ShortcutManager.FLAG_MATCH_PINNED), + "ms2", "ms3", "s1", "s2", "s3"); + + // Get dynamic and cached shortcuts + assertShortcutIds(mManager.getShortcuts( + ShortcutManager.FLAG_MATCH_DYNAMIC | ShortcutManager.FLAG_MATCH_CACHED), + "s1", "s2", "s3"); + + // Get pinned and cached shortcuts + assertShortcutIds(mManager.getShortcuts( + ShortcutManager.FLAG_MATCH_PINNED | ShortcutManager.FLAG_MATCH_CACHED), + "ms2", "ms3", "s1", "s2", "s3"); + + // Remove a dynamic cached shortcut + mManager.removeDynamicShortcuts(list("s1")); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), "s2", "s3"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s1", "s2"); + + // Remove a dynamic cached and pinned shortcut + mManager.removeDynamicShortcuts(list("s2")); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), "s3"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED), + "ms2", "ms3", "s2", "s3"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s1", "s2"); + } + + public void testCachedShortcuts() { + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"), + makeShortcut("s3"), makeShortcut("s4")))); + }); + + // Pin s2 + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), + HANDLE_USER_0); + }); + + // Cache 2, 3 and 4 + setCaller(CALLING_PACKAGE_1); + getCallerShortcut("s2").setCached(); + getCallerShortcut("s3").setCached(); + getCallerShortcut("s4").setCached(); + + // Get dynamic shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), + "s1", "s2", "s3", "s4"); + // Get pinned shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED), + "s2"); + // Get cached shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s2", "s3", "s4"); + + // Remove a dynamic cached shortcut + mManager.removeDynamicShortcuts(list("s3")); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), + "s1", "s2", "s4"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s2", "s3", "s4"); + + // Remove dynamic cached long lived shortcuts + mManager.removeLongLivedShortcuts(list("s3", "s4")); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), + "s1", "s2"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s2"); + + // Remove a dynamic cached pinned long lived shortcut + mManager.removeLongLivedShortcuts(list("s2")); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), + "s1"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED)); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED), + "s2"); + } + // === Test for launcher side APIs === public void testGetShortcuts() { @@ -1366,6 +1503,54 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); // TODO More tests: pinned but dynamic. + + // Cache some shortcuts + setCaller(CALLING_PACKAGE_1); + getCallerShortcut("s1").setCached(); + + setCaller(CALLING_PACKAGE_2); + getCallerShortcut("s4").setCached(); + + setCaller(CALLING_PACKAGE_3); + getCallerShortcut("s3").setCached(); + + setCaller(LAUNCHER_1); + + // Cached ones only + assertShortcutIds(assertAllNotKeyFieldsOnly( + mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 0, CALLING_PACKAGE_2, + /* activity =*/ null, + ShortcutQuery.FLAG_MATCH_CACHED), + getCallingUser())), + "s4"); + + // All packages. + assertShortcutIds(assertAllNotKeyFieldsOnly( + mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 0, /* package= */ null, + /* activity =*/ null, + ShortcutQuery.FLAG_MATCH_CACHED), + getCallingUser())), + "s1", "s4", "s3"); + + assertExpectException( + IllegalArgumentException.class, "package name must also be set", () -> { + mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 0, /* package= */ null, list("id"), + /* activity =*/ null, /* flags */ 0), getCallingUser()); + }); + + // Change Launcher. Cached shortcuts are the same for all launchers. + setCaller(LAUNCHER_2); + // All packages. + assertShortcutIds(assertAllNotKeyFieldsOnly( + mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 0, /* package= */ null, + /* activity =*/ null, + ShortcutQuery.FLAG_MATCH_CACHED), + getCallingUser())), + "s1", "s4", "s3"); } public void testGetShortcuts_shortcutKinds() throws Exception {