Adds methods in LauncherApps to allow cache/uncache shortcuts
Bug: 148084870
Test: atest com.android.server.pm.ShortcutManagerTest1 \
com.android.server.pm.ShortcutManagerTest2 \
com.android.server.pm.ShortcutManagerTest3 \
com.android.server.pm.ShortcutManagerTest4 \
com.android.server.pm.ShortcutManagerTest5 \
com.android.server.pm.ShortcutManagerTest6 \
com.android.server.pm.ShortcutManagerTest7 \
com.android.server.pm.ShortcutManagerTest8 \
com.android.server.pm.ShortcutManagerTest9 \
com.android.server.pm.ShortcutManagerTest10
Change-Id: I012bcc39194616a895606cdb9beb57a0f4af11d5
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 0492359..38a9ac4a 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -98,4 +98,9 @@
in ComponentName componentName, int flags, in IShortcutChangeCallback callback,
int callbackId);
void unregisterShortcutChangeCallback(String callingPackage, int callbackId);
+
+ void cacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
+ in UserHandle user);
+ void uncacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
+ in UserHandle user);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 73c9e4d..d253278 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -1089,6 +1090,61 @@
}
/**
+ * Mark shortcuts as cached for a package.
+ *
+ * <p>Only dynamic long lived shortcuts can be cached. None dynamic or non long lived shortcuts
+ * in the list will be ignored.
+ *
+ * <p>Unlike pinned shortcuts, where different callers can have different sets of pinned
+ * shortcuts, cached state is per shortcut only, and even if multiple callers cache the same
+ * shortcut, it can be uncached by any valid caller.
+ *
+ * @param packageName The target package name.
+ * @param shortcutIds The IDs of the shortcut to be cached.
+ * @param user The UserHandle of the profile.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
+ public void cacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+ @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.cacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove cached flag from shortcuts for a package.
+ *
+ * @param packageName The target package name.
+ * @param shortcutIds The IDs of the shortcut to be uncached.
+ * @param user The UserHandle of the profile.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
+ public void uncacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+ @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.uncacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide kept for testing.
*/
@Deprecated
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index a11a1dd..a69905e 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -85,4 +85,11 @@
public abstract boolean isForegroundDefaultLauncher(@NonNull String callingPackage,
int callingUid);
+
+ public abstract void cacheShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId);
+ public abstract void uncacheShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId);
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 3e64e98..0cd1c25 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -661,6 +661,23 @@
}
}
+ private void ensureStrictAccessShortcutsPermission(@NonNull String callingPackage) {
+ verifyCallingPackage(callingPackage);
+ if (!injectHasAccessShortcutsPermission(injectBinderCallingPid(),
+ injectBinderCallingUid())) {
+ throw new SecurityException("Caller can't access shortcut information");
+ }
+ }
+
+ /**
+ * Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
+ */
+ @VisibleForTesting
+ boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
+ return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
String packageName, List shortcutIds, List<LocusId> locusIds,
@@ -710,6 +727,30 @@
}
@Override
+ public void cacheShortcuts(String callingPackage, String packageName, List<String> ids,
+ UserHandle targetUser) {
+ ensureStrictAccessShortcutsPermission(callingPackage);
+ if (!canAccessProfile(targetUser.getIdentifier(), "Cannot cache shortcuts")) {
+ return;
+ }
+
+ mShortcutServiceInternal.cacheShortcuts(getCallingUserId(),
+ callingPackage, packageName, ids, targetUser.getIdentifier());
+ }
+
+ @Override
+ public void uncacheShortcuts(String callingPackage, String packageName, List<String> ids,
+ UserHandle targetUser) {
+ ensureStrictAccessShortcutsPermission(callingPackage);
+ if (!canAccessProfile(targetUser.getIdentifier(), "Cannot uncache shortcuts")) {
+ return;
+ }
+
+ mShortcutServiceInternal.uncacheShortcuts(getCallingUserId(),
+ callingPackage, packageName, ids, targetUser.getIdentifier());
+ }
+
+ @Override
public int getShortcutIconResId(String callingPackage, String packageName, String id,
int targetUserId) {
ensureShortcutPermission(callingPackage);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index d16c074..377fd16 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1704,7 +1704,7 @@
ShortcutInfo.validateIcon(shortcut.getIcon());
}
- shortcut.replaceFlags(0);
+ shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED);
}
private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
@@ -2758,6 +2758,68 @@
}
@Override
+ public void cacheShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId) {
+ updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds,
+ userId, /* doCache= */ true);
+ }
+
+ @Override
+ public void uncacheShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId) {
+ updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds,
+ userId, /* doCache= */ false);
+ }
+
+ private void updateCachedShortcutsInternal(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId, boolean doCache) {
+ // Calling permission must be checked by LauncherAppsImpl.
+ Preconditions.checkStringNotEmpty(packageName, "packageName");
+ Objects.requireNonNull(shortcutIds, "shortcutIds");
+
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+ throwIfUserLockedL(launcherUserId);
+
+ final int idSize = shortcutIds.size();
+ final ShortcutPackage sp = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (idSize == 0 || sp == null) {
+ return;
+ }
+
+ for (int i = 0; i < idSize; i++) {
+ final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i));
+ final ShortcutInfo si = sp.findShortcutById(id);
+ if (si == null || doCache == si.isCached()) {
+ continue;
+ }
+
+ if (doCache) {
+ if (si.isDynamic() && si.isLongLived()) {
+ si.addFlags(ShortcutInfo.FLAG_CACHED);
+ } else {
+ Log.w(TAG, "Only dynamic long lived shortcuts can get cached. Ignoring"
+ + "shortcut " + si.getId());
+ }
+ } else {
+ if (si.isDynamic()) {
+ si.clearFlags(ShortcutInfo.FLAG_CACHED);
+ } else {
+ sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+ }
+ }
+ }
+ }
+ packageShortcutsChanged(packageName, userId);
+
+ verifyStates();
+ }
+
+ @Override
public Intent[] createShortcutIntents(int launcherUserId,
@NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId,
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 3d190be..77f842a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -556,6 +556,11 @@
void injectRestoreCallingIdentity(long token) {
mInjectedCallingUid = (int) token;
}
+
+ @Override
+ boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
+ return true;
+ }
}
protected class LauncherAppsTestable extends LauncherApps {
@@ -1617,6 +1622,22 @@
}
/**
+ * Make a long lived shortcut with an ID.
+ */
+ protected ShortcutInfo makeLongLivedShortcut(String id) {
+ final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id)
+ .setActivity(new ComponentName(mClientContext.getPackageName(), "main"))
+ .setShortLabel("title-" + id)
+ .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class))
+ .setLongLived(true);
+ final ShortcutInfo s = b.build();
+
+ s.setTimestamp(mInjectedCurrentTimeMillis); // HACK
+
+ return s;
+ }
+
+ /**
* Make an intent.
*/
protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
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 63da5fb..f036708 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1240,7 +1240,7 @@
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ makeLongLivedShortcut("s1"), makeLongLivedShortcut("s2"), makeShortcut("s3"))));
});
// Pin 2 and 3
@@ -1250,9 +1250,12 @@
});
// Cache 1 and 2
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
+ HANDLE_USER_0);
+ });
+
setCaller(CALLING_PACKAGE_1);
- getCallerShortcut("s1").setCached();
- getCallerShortcut("s2").setCached();
// Get manifest shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_MANIFEST),
@@ -1315,8 +1318,9 @@
public void testCachedShortcuts() {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"),
- makeShortcut("s3"), makeShortcut("s4"))));
+ assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
+ makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
+ makeLongLivedShortcut("s4"))));
});
// Pin s2
@@ -1325,11 +1329,13 @@
HANDLE_USER_0);
});
- // Cache 2, 3 and 4
+ // Cache some, but non long lived shortcuts will be ignored.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s4"),
+ HANDLE_USER_0);
+ });
+
setCaller(CALLING_PACKAGE_1);
- getCallerShortcut("s2").setCached();
- getCallerShortcut("s3").setCached();
- getCallerShortcut("s4").setCached();
// Get dynamic shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
@@ -1339,27 +1345,37 @@
"s2");
// Get cached shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s3", "s4");
+ "s2", "s4");
// Remove a dynamic cached shortcut
- mManager.removeDynamicShortcuts(list("s3"));
+ mManager.removeDynamicShortcuts(list("s4"));
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s2", "s4");
+ "s1", "s2", "s3");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s3", "s4");
+ "s2", "s4");
- // Remove dynamic cached long lived shortcuts
- mManager.removeLongLivedShortcuts(list("s3", "s4"));
- assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s2");
+ // uncache a non-dynamic shortcut. Should be removed.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"),
+ HANDLE_USER_0);
+ });
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
"s2");
+ // Cache another shortcut
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"),
+ HANDLE_USER_0);
+ });
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+ "s2", "s3");
+
// 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));
+ "s1", "s3");
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+ "s3");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
"s2");
}
@@ -1371,7 +1387,7 @@
// Set up shortcuts.
setCaller(CALLING_PACKAGE_1);
- final ShortcutInfo s1_1 = makeShortcut("s1");
+ final ShortcutInfo s1_1 = makeLongLivedShortcut("s1");
final ShortcutInfo s1_2 = makeShortcutWithLocusId("s2", makeLocusId("l1"));
assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
@@ -1395,6 +1411,8 @@
setCaller(CALLING_PACKAGE_3);
final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
+ s3_2.setLongLived();
+
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
@@ -1535,26 +1553,20 @@
// 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);
+ // Cache some shortcuts. Only long lived shortcuts can get cached.
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser());
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser());
+
// Cached ones only
assertShortcutIds(assertAllNotKeyFieldsOnly(
mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 0, CALLING_PACKAGE_2,
+ /* time =*/ 0, CALLING_PACKAGE_3,
/* activity =*/ null,
ShortcutQuery.FLAG_MATCH_CACHED),
getCallingUser())),
- "s4");
+ "s3");
// All packages.
assertShortcutIds(assertAllNotKeyFieldsOnly(
@@ -1563,7 +1575,7 @@
/* activity =*/ null,
ShortcutQuery.FLAG_MATCH_CACHED),
getCallingUser())),
- "s1", "s4", "s3");
+ "s1", "s3");
assertExpectException(
IllegalArgumentException.class, "package name must also be set", () -> {
@@ -1581,7 +1593,7 @@
/* activity =*/ null,
ShortcutQuery.FLAG_MATCH_CACHED),
getCallingUser())),
- "s1", "s4", "s3");
+ "s1", "s3");
}
public void testGetShortcuts_shortcutKinds() throws Exception {