From 480d532c962f321568a659918299855e286658ca Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Tue, 13 Apr 2021 09:46:40 -0700 Subject: [PATCH] Replace oneway shortcut api with AndroidFuture Oneway calls to the system process can cause more trouble along those lines than they solve. Asynchronous incall pressure to the system process is worse than synchronous incall pressure: it causes catastrophic failure modes and is also much harder to diagnose when problems happen. It's also much harder to guarantee transactionality when the caller side is asynchronous. Bug: 184878227 Test: atest ShortcutManagerTest1 ShortcutManagerTest2 ShortcutManagerTest3 ShortcutManagerTest4 ShortcutManagerTest5 ShortcutManagerTest6 ShortcutManagerTest7 ShortcutManagerTest8 ShortcutManagerTest9 ShortcutManagerTest10 ShortcutManagerTest11 Test: atest CtsShortcutManagerTestCases Change-Id: I4ec6b188f079c018902bedf32198c04e9c45a60d Change-Id: I854de382ee6bdc5ae32b6874b7d875dfe6c03c40 --- .../android/content/pm/IShortcutService.aidl | 49 +- .../android/content/pm/ShortcutManager.java | 119 +- .../android/server/pm/ShortcutPackage.java | 14 +- .../android/server/pm/ShortcutService.java | 1399 ++++++++++------- 4 files changed, 885 insertions(+), 696 deletions(-) diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index c91334a5470fc..9d381ef586d48 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -26,29 +26,29 @@ import com.android.internal.infra.AndroidFuture; /** {@hide} */ interface IShortcutService { - oneway void setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, - int userId, in AndroidFuture callback); + AndroidFuture setDynamicShortcuts(String packageName, + in ParceledListSlice shortcutInfoList, int userId); - oneway void addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, - int userId, in AndroidFuture callback); + AndroidFuture addDynamicShortcuts(String packageName, + in ParceledListSlice shortcutInfoList, int userId); - oneway void removeDynamicShortcuts(String packageName, in List shortcutIds, int userId); + AndroidFuture removeDynamicShortcuts(String packageName, in List shortcutIds, int userId); - oneway void removeAllDynamicShortcuts(String packageName, int userId); + AndroidFuture removeAllDynamicShortcuts(String packageName, int userId); - oneway void updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId, - in AndroidFuture callback); + AndroidFuture updateShortcuts(String packageName, in ParceledListSlice shortcuts, + int userId); - oneway void requestPinShortcut(String packageName, in ShortcutInfo shortcut, - in IntentSender resultIntent, int userId, in AndroidFuture callback); + AndroidFuture requestPinShortcut(String packageName, in ShortcutInfo shortcut, + in IntentSender resultIntent, int userId); - oneway void createShortcutResultIntent(String packageName, in ShortcutInfo shortcut, - int userId, in AndroidFuture callback); + AndroidFuture createShortcutResultIntent(String packageName, in ShortcutInfo shortcut, + int userId); - oneway void disableShortcuts(String packageName, in List shortcutIds, + AndroidFuture disableShortcuts(String packageName, in List shortcutIds, CharSequence disabledMessage, int disabledMessageResId, int userId); - oneway void enableShortcuts(String packageName, in List shortcutIds, int userId); + AndroidFuture enableShortcuts(String packageName, in List shortcutIds, int userId); int getMaxShortcutCountPerActivity(String packageName, int userId); @@ -58,31 +58,30 @@ interface IShortcutService { int getIconMaxDimensions(String packageName, int userId); - oneway void reportShortcutUsed(String packageName, String shortcutId, int userId); + AndroidFuture reportShortcutUsed(String packageName, String shortcutId, int userId); - oneway void resetThrottling(); // system only API for developer opsions + void resetThrottling(); // system only API for developer opsions - oneway void onApplicationActive(String packageName, int userId); // system only API for sysUI + AndroidFuture onApplicationActive(String packageName, int userId); // system only API for sysUI byte[] getBackupPayload(int user); - oneway void applyRestore(in byte[] payload, int user); + AndroidFuture applyRestore(in byte[] payload, int user); boolean isRequestPinItemSupported(int user, int requestType); // System API used by framework's ShareSheet (ChooserActivity) - oneway void getShareTargets(String packageName, in IntentFilter filter, int userId, - in AndroidFuture callback); + AndroidFuture getShareTargets(String packageName, in IntentFilter filter, + int userId); boolean hasShareTargets(String packageName, String packageToCheck, int userId); - oneway void removeLongLivedShortcuts(String packageName, in List shortcutIds, int userId); + AndroidFuture removeLongLivedShortcuts(String packageName, in List shortcutIds, int userId); - oneway void getShortcuts(String packageName, int matchFlags, int userId, - in AndroidFuture> callback); + AndroidFuture getShortcuts(String packageName, int matchFlags, int userId); - oneway void pushDynamicShortcut(String packageName, in ShortcutInfo shortcut, int userId); + AndroidFuture pushDynamicShortcut(String packageName, in ShortcutInfo shortcut, int userId); - oneway void updateShortcutVisibility(String callingPkg, String packageName, + AndroidFuture updateShortcutVisibility(String callingPkg, String packageName, in byte[] certificate, in boolean visible, int userId); } diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index f584ff33fa50d..2a36c11445c9f 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -145,14 +145,13 @@ public class ShortcutManager { */ @WorkerThread public boolean setDynamicShortcuts(@NonNull List shortcutInfoList) { - final AndroidFuture future = new AndroidFuture<>(); try { - mService.setDynamicShortcuts(mContext.getPackageName(), - new ParceledListSlice(shortcutInfoList), injectMyUserId(), future); + return ((boolean) getFutureOrThrow(mService.setDynamicShortcuts( + mContext.getPackageName(), new ParceledListSlice( + shortcutInfoList), injectMyUserId()))); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future); } /** @@ -167,14 +166,12 @@ public class ShortcutManager { @WorkerThread @NonNull public List getDynamicShortcuts() { - final AndroidFuture> future = new AndroidFuture<>(); try { - mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_DYNAMIC, injectMyUserId(), - future); + return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(), + FLAG_MATCH_DYNAMIC, injectMyUserId())).getList(); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future).getList(); } /** @@ -189,14 +186,12 @@ public class ShortcutManager { @WorkerThread @NonNull public List getManifestShortcuts() { - final AndroidFuture> future = new AndroidFuture<>(); try { - mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_MANIFEST, injectMyUserId(), - future); + return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(), + FLAG_MATCH_MANIFEST, injectMyUserId())).getList(); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future).getList(); } /** @@ -220,13 +215,12 @@ public class ShortcutManager { @WorkerThread @NonNull public List getShortcuts(@ShortcutMatchFlags int matchFlags) { - final AndroidFuture> future = new AndroidFuture<>(); try { - mService.getShortcuts(mContext.getPackageName(), matchFlags, injectMyUserId(), future); + return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(), matchFlags, + injectMyUserId())).getList(); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future).getList(); } /** @@ -244,14 +238,13 @@ public class ShortcutManager { */ @WorkerThread public boolean addDynamicShortcuts(@NonNull List shortcutInfoList) { - final AndroidFuture future = new AndroidFuture<>(); try { - mService.addDynamicShortcuts(mContext.getPackageName(), - new ParceledListSlice(shortcutInfoList), injectMyUserId(), future); + return (boolean) getFutureOrThrow(mService.addDynamicShortcuts( + mContext.getPackageName(), new ParceledListSlice(shortcutInfoList), + injectMyUserId())); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future); } /** @@ -261,8 +254,8 @@ public class ShortcutManager { */ public void removeDynamicShortcuts(@NonNull List shortcutIds) { try { - mService.removeDynamicShortcuts(mContext.getPackageName(), shortcutIds, - injectMyUserId()); + getFutureOrThrow(mService.removeDynamicShortcuts(mContext.getPackageName(), shortcutIds, + injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -275,7 +268,8 @@ public class ShortcutManager { */ public void removeAllDynamicShortcuts() { try { - mService.removeAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId()); + getFutureOrThrow(mService.removeAllDynamicShortcuts(mContext.getPackageName(), + injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -288,8 +282,8 @@ public class ShortcutManager { */ public void removeLongLivedShortcuts(@NonNull List shortcutIds) { try { - mService.removeLongLivedShortcuts(mContext.getPackageName(), shortcutIds, - injectMyUserId()); + getFutureOrThrow(mService.removeLongLivedShortcuts(mContext.getPackageName(), + shortcutIds, injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -307,14 +301,12 @@ public class ShortcutManager { @WorkerThread @NonNull public List getPinnedShortcuts() { - final AndroidFuture> future = new AndroidFuture<>(); try { - mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_PINNED, injectMyUserId(), - future); + return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(), + FLAG_MATCH_PINNED, injectMyUserId())).getList(); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future).getList(); } /** @@ -331,14 +323,12 @@ public class ShortcutManager { */ @WorkerThread public boolean updateShortcuts(@NonNull List shortcutInfoList) { - final AndroidFuture future = new AndroidFuture<>(); try { - mService.updateShortcuts(mContext.getPackageName(), - new ParceledListSlice(shortcutInfoList), injectMyUserId(), future); + return (boolean) getFutureOrThrow(mService.updateShortcuts(mContext.getPackageName(), + new ParceledListSlice(shortcutInfoList), injectMyUserId())); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future); } /** @@ -352,9 +342,9 @@ public class ShortcutManager { */ public void disableShortcuts(@NonNull List shortcutIds) { try { - mService.disableShortcuts(mContext.getPackageName(), shortcutIds, + getFutureOrThrow(mService.disableShortcuts(mContext.getPackageName(), shortcutIds, /* disabledMessage =*/ null, /* disabledMessageResId =*/ 0, - injectMyUserId()); + injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -365,9 +355,9 @@ public class ShortcutManager { */ public void disableShortcuts(@NonNull List shortcutIds, int disabledMessageResId) { try { - mService.disableShortcuts(mContext.getPackageName(), shortcutIds, + getFutureOrThrow(mService.disableShortcuts(mContext.getPackageName(), shortcutIds, /* disabledMessage =*/ null, disabledMessageResId, - injectMyUserId()); + injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -393,9 +383,9 @@ public class ShortcutManager { */ public void disableShortcuts(@NonNull List shortcutIds, CharSequence disabledMessage) { try { - mService.disableShortcuts(mContext.getPackageName(), shortcutIds, + getFutureOrThrow(mService.disableShortcuts(mContext.getPackageName(), shortcutIds, disabledMessage, /* disabledMessageResId =*/ 0, - injectMyUserId()); + injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -411,7 +401,8 @@ public class ShortcutManager { */ public void enableShortcuts(@NonNull List shortcutIds) { try { - mService.enableShortcuts(mContext.getPackageName(), shortcutIds, injectMyUserId()); + getFutureOrThrow(mService.enableShortcuts( + mContext.getPackageName(), shortcutIds, injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -532,8 +523,8 @@ public class ShortcutManager { */ public void reportShortcutUsed(String shortcutId) { try { - mService.reportShortcutUsed(mContext.getPackageName(), shortcutId, - injectMyUserId()); + getFutureOrThrow(mService.reportShortcutUsed(mContext.getPackageName(), shortcutId, + injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -610,14 +601,12 @@ public class ShortcutManager { @WorkerThread public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut, @Nullable IntentSender resultIntent) { - final AndroidFuture future = new AndroidFuture<>(); try { - mService.requestPinShortcut(mContext.getPackageName(), shortcut, - resultIntent, injectMyUserId(), future); + return (boolean) getFutureOrThrow(mService.requestPinShortcut(mContext.getPackageName(), + shortcut, resultIntent, injectMyUserId())); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future); } /** @@ -639,14 +628,12 @@ public class ShortcutManager { */ @WorkerThread public Intent createShortcutResultIntent(@NonNull ShortcutInfo shortcut) { - final AndroidFuture future = new AndroidFuture<>(); try { - mService.createShortcutResultIntent(mContext.getPackageName(), shortcut, - injectMyUserId(), future); + return getFutureOrThrow(mService.createShortcutResultIntent(mContext.getPackageName(), + shortcut, injectMyUserId())); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future); } /** @@ -659,7 +646,7 @@ public class ShortcutManager { */ public void onApplicationActive(@NonNull String packageName, @UserIdInt int userId) { try { - mService.onApplicationActive(packageName, userId); + getFutureOrThrow(mService.onApplicationActive(packageName, userId)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -684,13 +671,12 @@ public class ShortcutManager { @SystemApi @RequiresPermission(Manifest.permission.MANAGE_APP_PREDICTIONS) public List getShareTargets(@NonNull IntentFilter filter) { - final AndroidFuture future = new AndroidFuture<>(); try { - mService.getShareTargets(mContext.getPackageName(), filter, injectMyUserId(), future); + return getFutureOrThrow(mService.getShareTargets(mContext.getPackageName(), filter, + injectMyUserId())).getList(); } catch (RemoteException e) { - future.completeExceptionally(e); + throw e.rethrowFromSystemServer(); } - return getFutureOrThrow(future).getList(); } /** @@ -797,7 +783,8 @@ public class ShortcutManager { */ public void pushDynamicShortcut(@NonNull ShortcutInfo shortcut) { try { - mService.pushDynamicShortcut(mContext.getPackageName(), shortcut, injectMyUserId()); + getFutureOrThrow(mService.pushDynamicShortcut( + mContext.getPackageName(), shortcut, injectMyUserId())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -813,8 +800,8 @@ public class ShortcutManager { public void updateShortcutVisibility(@NonNull final String packageName, @Nullable final byte[] certificate, final boolean visible) { try { - mService.updateShortcutVisibility(mContext.getPackageName(), packageName, certificate, - visible, injectMyUserId()); + getFutureOrThrow(mService.updateShortcutVisibility(mContext.getPackageName(), + packageName, certificate, visible, injectMyUserId())); } 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 ada65860018a6..83fe095769f06 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -158,6 +158,8 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String KEY_BITMAPS = "bitmaps"; private static final String KEY_BITMAP_BYTES = "bitmapBytes"; + private final Object mLock = new Object(); + /** * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs. */ @@ -168,6 +170,11 @@ class ShortcutPackage extends ShortcutPackageItem { */ private final ArrayList mShareTargets = new ArrayList<>(0); + /** + * All external packages that have gained access to the shortcuts from this package + */ + private final Map mPackageIdentifiers = new ArrayMap<>(0); + /** * # of times the package has called rate-limited APIs. */ @@ -182,13 +189,6 @@ class ShortcutPackage extends ShortcutPackageItem { private long mLastKnownForegroundElapsedTime; - private final Object mLock = new Object(); - - /** - * All external packages that have gained access to the shortcuts from this package - */ - private final Map mPackageIdentifiers = new ArrayMap<>(0); - private boolean mIsInitilized; private ShortcutPackage(ShortcutUser shortcutUser, diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index ee3335fba9152..a69e9db753e25 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1692,6 +1692,17 @@ public class ShortcutService extends IShortcutService.Stub { "Ephemeral apps can't use ShortcutManager"); } + private boolean verifyCaller(@NonNull String packageName, @UserIdInt int userId, + @NonNull AndroidFuture ret) { + try { + verifyCaller(packageName, userId); + } catch (Exception e) { + ret.completeExceptionally(e); + return false; + } + return true; + } + private void verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si) { if (si == null) { return; @@ -1719,6 +1730,11 @@ public class ShortcutService extends IShortcutService.Stub { new Thread(r).start(); } + void injectPostToHandlerIfAppSearch(Runnable r) { + // TODO: move to background thread when app search is enabled. + r.run(); + } + /** * @throws IllegalArgumentException if {@code numShortcuts} is bigger than * {@link #getMaxActivityShortcuts()}. @@ -1904,351 +1920,400 @@ public class ShortcutService extends IShortcutService.Stub { // === APIs === @Override - public void setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, - @UserIdInt int userId, @NonNull AndroidFuture callback) { - try { - verifyCaller(packageName, userId); - - final List newShortcuts = (List) shortcutInfoList.getList(); - verifyShortcutInfoPackages(packageName, newShortcuts); - final int size = newShortcuts.size(); - - final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( - injectBinderCallingPid(), injectBinderCallingUid()); - - List changedShortcuts = null; - List removedShortcuts = null; - - synchronized (mLock) { - throwIfUserLockedL(userId); - - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, - userId); - - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); - ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); - - fillInDefaultActivity(newShortcuts); - - ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); - - // Throttling. - if (!ps.tryApiCall(unlimited)) { - callback.complete(false); - return; - } - - // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). - ps.clearAllImplicitRanks(); - assignImplicitRanks(newShortcuts); - - for (int i = 0; i < size; i++) { - fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); - } - - ArrayList cachedOrPinned = new ArrayList<>(); - ps.findAll(cachedOrPinned, AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, - (ShortcutInfo si) -> si.isVisibleToPublisher() - && si.isDynamic() && (si.isCached() || si.isPinned()), - ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); - - // First, remove all un-pinned and non-cached; dynamic shortcuts - removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); - - // Then, add/update all. We need to make sure to take over "pinned" flag. - for (int i = 0; i < size; i++) { - final ShortcutInfo newShortcut = newShortcuts.get(i); - ps.addOrReplaceDynamicShortcut(newShortcut); - } - - // Lastly, adjust the ranks. - ps.adjustRanks(); - - changedShortcuts = prepareChangedShortcuts( - cachedOrPinned, newShortcuts, removedShortcuts, ps); - } - - packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); - - verifyStates(); - - callback.complete(true); - } catch (Exception e) { - callback.completeExceptionally(e); + public AndroidFuture setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( + injectBinderCallingPid(), injectBinderCallingUid()); + injectPostToHandlerIfAppSearch(() -> { + try { + final List newShortcuts = + (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); + final int size = newShortcuts.size(); + + List changedShortcuts = null; + List removedShortcuts = null; + + synchronized (mLock) { + throwIfUserLockedL(userId); + + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); + + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); + + fillInDefaultActivity(newShortcuts); + + ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); + + // Throttling. + if (!ps.tryApiCall(unlimited)) { + ret.complete(false); + return; + } + + // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). + ps.clearAllImplicitRanks(); + assignImplicitRanks(newShortcuts); + + for (int i = 0; i < size; i++) { + fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); + } + + ArrayList cachedOrPinned = new ArrayList<>(); + ps.findAll(cachedOrPinned, + AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, + (ShortcutInfo si) -> si.isVisibleToPublisher() + && si.isDynamic() && (si.isCached() || si.isPinned()), + ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); + + // First, remove all un-pinned and non-cached; dynamic shortcuts + removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); + + // Then, add/update all. We need to make sure to take over "pinned" flag. + for (int i = 0; i < size; i++) { + final ShortcutInfo newShortcut = newShortcuts.get(i); + ps.addOrReplaceDynamicShortcut(newShortcut); + } + + // Lastly, adjust the ranks. + ps.adjustRanks(); + + changedShortcuts = prepareChangedShortcuts( + cachedOrPinned, newShortcuts, removedShortcuts, ps); + } + + + packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + + verifyStates(); + } catch (Exception e) { + ret.completeExceptionally(e); + } + ret.complete(true); + }); + return ret; } @Override - public void updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, - @UserIdInt int userId, AndroidFuture callback) { - try { - verifyCaller(packageName, userId); - - final List newShortcuts = (List) shortcutInfoList.getList(); - verifyShortcutInfoPackages(packageName, newShortcuts); - final int size = newShortcuts.size(); - - final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( - injectBinderCallingPid(), injectBinderCallingUid()); - - final List changedShortcuts = new ArrayList<>(1); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, - userId); - - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); - ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); - - // For update, don't fill in the default activity. Having null activity means - // "don't update the activity" here. - - ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); - - // Throttling. - if (!ps.tryApiCall(unlimited)) { - callback.complete(false); - return; - } - - // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). - ps.clearAllImplicitRanks(); - assignImplicitRanks(newShortcuts); - - for (int i = 0; i < size; i++) { - final ShortcutInfo source = newShortcuts.get(i); - fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); - - ps.mutateShortcut(source.getId(), null, target -> { - // Invisible shortcuts can't be updated. - if (target == null || !target.isVisibleToPublisher()) { - return; - } - - if (target.isEnabled() != source.isEnabled()) { - Slog.w(TAG, "ShortcutInfo.enabled cannot be changed with" - + " updateShortcuts()"); - } - - if (target.isLongLived() != source.isLongLived()) { - Slog.w(TAG, - "ShortcutInfo.longLived cannot be changed with" - + " updateShortcuts()"); - } - - // When updating the rank, we need to insert between existing ranks, so set - // this setRankChanged, and also copy the implicit rank fo adjustRanks(). - if (source.hasRank()) { - target.setRankChanged(); - target.setImplicitRank(source.getImplicitRank()); - } - - final boolean replacingIcon = (source.getIcon() != null); - if (replacingIcon) { - removeIconLocked(target); - } - - // Note copyNonNullFieldsFrom() does the "updatable with?" check too. - target.copyNonNullFieldsFrom(source); - target.setTimestamp(injectCurrentTimeMillis()); - - if (replacingIcon) { - saveIconAndFixUpShortcutLocked(target); - } - - // When we're updating any resource related fields, re-extract the res - // names and the values. - if (replacingIcon || source.hasStringResources()) { - fixUpShortcutResourceNamesAndValues(target); - } - - changedShortcuts.add(target); - }); - } - - // Lastly, adjust the ranks. - ps.adjustRanks(); - } - packageShortcutsChanged(packageName, userId, - changedShortcuts.isEmpty() ? null : changedShortcuts, null); - - verifyStates(); - - callback.complete(true); - } catch (Exception e) { - callback.completeExceptionally(e); + public AndroidFuture updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( + injectBinderCallingPid(), injectBinderCallingUid()); + injectPostToHandlerIfAppSearch(() -> { + try { + final List newShortcuts = + (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); + final int size = newShortcuts.size(); + + final List changedShortcuts = new ArrayList<>(1); + + synchronized (mLock) { + throwIfUserLockedL(userId); + + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); + + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); + + // For update, don't fill in the default activity. Having null activity means + // "don't update the activity" here. + + ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); + + // Throttling. + if (!ps.tryApiCall(unlimited)) { + ret.complete(false); + return; + } + + // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). + ps.clearAllImplicitRanks(); + assignImplicitRanks(newShortcuts); + + for (int i = 0; i < size; i++) { + final ShortcutInfo source = newShortcuts.get(i); + fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); + + ps.mutateShortcut(source.getId(), null, target -> { + // Invisible shortcuts can't be updated. + if (target == null || !target.isVisibleToPublisher()) { + return; + } + + if (target.isEnabled() != source.isEnabled()) { + Slog.w(TAG, "ShortcutInfo.enabled cannot be changed with" + + " updateShortcuts()"); + } + + if (target.isLongLived() != source.isLongLived()) { + Slog.w(TAG, + "ShortcutInfo.longLived cannot be changed with" + + " updateShortcuts()"); + } + + // When updating the rank, we need to insert between existing ranks, + // so set this setRankChanged, and also copy the implicit rank fo + // adjustRanks(). + if (source.hasRank()) { + target.setRankChanged(); + target.setImplicitRank(source.getImplicitRank()); + } + + final boolean replacingIcon = (source.getIcon() != null); + if (replacingIcon) { + removeIconLocked(target); + } + + // Note copyNonNullFieldsFrom() does the "updatable with?" check too. + target.copyNonNullFieldsFrom(source); + target.setTimestamp(injectCurrentTimeMillis()); + + if (replacingIcon) { + saveIconAndFixUpShortcutLocked(target); + } + + // When we're updating any resource related fields, re-extract the res + // names and the values. + if (replacingIcon || source.hasStringResources()) { + fixUpShortcutResourceNamesAndValues(target); + } + + changedShortcuts.add(target); + }); + } + + // Lastly, adjust the ranks. + ps.adjustRanks(); + } + packageShortcutsChanged(packageName, userId, + changedShortcuts.isEmpty() ? null : changedShortcuts, null); + + verifyStates(); + + ret.complete(true); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } @Override - public void addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, - @UserIdInt int userId, AndroidFuture callback) { - try { - verifyCaller(packageName, userId); + public AndroidFuture addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; + } + final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( + injectBinderCallingPid(), injectBinderCallingUid()); + injectPostToHandlerIfAppSearch(() -> { + try { + final List newShortcuts = + (List) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); + final int size = newShortcuts.size(); - final List newShortcuts = (List) shortcutInfoList.getList(); - verifyShortcutInfoPackages(packageName, newShortcuts); - final int size = newShortcuts.size(); + List changedShortcuts = null; - final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( - injectBinderCallingPid(), injectBinderCallingUid()); + synchronized (mLock) { + throwIfUserLockedL(userId); - List changedShortcuts = null; + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); - synchronized (mLock) { - throwIfUserLockedL(userId); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, - userId); + fillInDefaultActivity(newShortcuts); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); - ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); + ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); - fillInDefaultActivity(newShortcuts); + // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). + ps.clearAllImplicitRanks(); + assignImplicitRanks(newShortcuts); - ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); + // Throttling. + if (!ps.tryApiCall(unlimited)) { + ret.complete(false); + return; + } + for (int i = 0; i < size; i++) { + final ShortcutInfo newShortcut = newShortcuts.get(i); - // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). - ps.clearAllImplicitRanks(); - assignImplicitRanks(newShortcuts); + // Validate the shortcut. + fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); - // Throttling. - if (!ps.tryApiCall(unlimited)) { - callback.complete(false); - return; + // When ranks are changing, we need to insert between ranks, so set the + // "rank changed" flag. + newShortcut.setRankChanged(); + + // Add it. + ps.addOrReplaceDynamicShortcut(newShortcut); + + if (changedShortcuts == null) { + changedShortcuts = new ArrayList<>(1); + } + changedShortcuts.add(newShortcut); + } + + // Lastly, adjust the ranks. + ps.adjustRanks(); } - for (int i = 0; i < size; i++) { - final ShortcutInfo newShortcut = newShortcuts.get(i); + packageShortcutsChanged(packageName, userId, changedShortcuts, null); + + verifyStates(); + + ret.complete(true); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; + } + + @Override + public AndroidFuture pushDynamicShortcut(String packageName, ShortcutInfo shortcut, + @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; + } + injectPostToHandlerIfAppSearch(() -> { + try { + verifyShortcutInfoPackage(packageName, shortcut); + + List changedShortcuts = new ArrayList<>(); + List removedShortcuts = null; + + synchronized (mLock) { + throwIfUserLockedL(userId); + + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); + + ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true); + fillInDefaultActivity(Arrays.asList(shortcut)); + + if (!shortcut.hasRank()) { + shortcut.setRank(0); + } + // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). + ps.clearAllImplicitRanks(); + shortcut.setImplicitRank(0); // Validate the shortcut. - fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); + fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false); // When ranks are changing, we need to insert between ranks, so set the // "rank changed" flag. - newShortcut.setRankChanged(); + shortcut.setRankChanged(); - // Add it. - ps.addOrReplaceDynamicShortcut(newShortcut); + // Push it. + boolean deleted = ps.pushDynamicShortcut(shortcut, changedShortcuts); - if (changedShortcuts == null) { - changedShortcuts = new ArrayList<>(1); + if (deleted) { + if (changedShortcuts.isEmpty()) { + ret.complete(null); + return; // Failed to push. + } + removedShortcuts = Collections.singletonList(changedShortcuts.get(0)); + changedShortcuts.clear(); } - changedShortcuts.add(newShortcut); + changedShortcuts.add(shortcut); + + // Lastly, adjust the ranks. + ps.adjustRanks(); } - // Lastly, adjust the ranks. - ps.adjustRanks(); + packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + + verifyStates(); + + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); } - packageShortcutsChanged(packageName, userId, changedShortcuts, null); - - verifyStates(); - - callback.complete(true); - } catch (Exception e) { - callback.completeExceptionally(e); - } + }); + return ret; } @Override - public void pushDynamicShortcut(String packageName, ShortcutInfo shortcut, - @UserIdInt int userId) { - verifyCaller(packageName, userId); - verifyShortcutInfoPackage(packageName, shortcut); - - List changedShortcuts = new ArrayList<>(); - List removedShortcuts = null; - - synchronized (mLock) { - throwIfUserLockedL(userId); - - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - - ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true); - fillInDefaultActivity(Arrays.asList(shortcut)); - - if (!shortcut.hasRank()) { - shortcut.setRank(0); - } - // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). - ps.clearAllImplicitRanks(); - shortcut.setImplicitRank(0); - - // Validate the shortcut. - fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false); - - // When ranks are changing, we need to insert between ranks, so set the - // "rank changed" flag. - shortcut.setRankChanged(); - - // Push it. - boolean deleted = ps.pushDynamicShortcut(shortcut, changedShortcuts); - - if (deleted) { - if (changedShortcuts.isEmpty()) { - return; // Failed to push. + public AndroidFuture updateShortcutVisibility(String callingPkg, String packageName, + byte[] certificate, boolean visible, int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + injectPostToHandlerIfAppSearch(() -> { + try { + synchronized (mLock) { + getPackageShortcutsForPublisherLocked(callingPkg, userId) + .updateVisibility(packageName, certificate, visible); } - removedShortcuts = Collections.singletonList(changedShortcuts.get(0)); - changedShortcuts.clear(); + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); } - changedShortcuts.add(shortcut); - - // Lastly, adjust the ranks. - ps.adjustRanks(); - } - - packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); - - verifyStates(); + }); + return ret; } @Override - public void updateShortcutVisibility(String callingPkg, String packageName, byte[] certificate, - boolean visible, int userId) { - synchronized (mLock) { - getPackageShortcutsForPublisherLocked(callingPkg, userId) - .updateVisibility(packageName, certificate, visible); - } - } - - @Override - public void requestPinShortcut(String packageName, ShortcutInfo shortcut, - IntentSender resultIntent, int userId, AndroidFuture callback) { - try { - Objects.requireNonNull(shortcut); - Objects.requireNonNull(callback); - Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); - callback.complete( - requestPinItem(packageName, userId, shortcut, null, null, resultIntent)); - } catch (Exception e) { - callback.completeExceptionally(e); - } - } - - @Override - public void createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId, - AndroidFuture callback) - throws RemoteException { - try { - Objects.requireNonNull(shortcut); - Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); - verifyCaller(packageName, userId); - verifyShortcutInfoPackage(packageName, shortcut); - - final Intent ret; - synchronized (mLock) { - throwIfUserLockedL(userId); - - // Send request to the launcher, if supported. - ret = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId); + public AndroidFuture requestPinShortcut(String packageName, ShortcutInfo shortcut, + IntentSender resultIntent, int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + final int callingPid = injectBinderCallingPid(); + final int callingUid = injectBinderCallingUid(); + injectPostToHandlerIfAppSearch(() -> { + try { + ret.complete( + requestPinItem(packageName, userId, shortcut, null, null, resultIntent, + callingPid, callingUid)); + } catch (Exception e) { + ret.completeExceptionally(e); } + }); + return ret; + } - verifyStates(); - callback.complete(ret); - } catch (Exception e) { - callback.completeExceptionally(e); + @Override + public AndroidFuture createShortcutResultIntent( + String packageName, ShortcutInfo shortcut, int userId) throws RemoteException { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + Objects.requireNonNull(shortcut); + Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); + verifyShortcutInfoPackage(packageName, shortcut); + final Intent intent; + synchronized (mLock) { + throwIfUserLockedL(userId); + + // Send request to the launcher, if supported. + intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, + userId); + } + + verifyStates(); + ret.complete(intent); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } /** @@ -2258,9 +2323,16 @@ public class ShortcutService extends IShortcutService.Stub { */ private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) { + return requestPinItem(callingPackage, userId, shortcut, appWidget, extras, resultIntent, + injectBinderCallingPid(), injectBinderCallingUid()); + } + + private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, + AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent, + int callingPid, int callingUid) { verifyCaller(callingPackage, userId); if (shortcut == null || !injectHasAccessShortcutsPermission( - injectBinderCallingPid(), injectBinderCallingUid())) { + callingPid, callingUid)) { // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS. verifyShortcutInfoPackage(callingPackage, shortcut); } @@ -2269,7 +2341,7 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { throwIfUserLockedL(userId); - Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()), + Preconditions.checkState(isUidForegroundLocked(callingUid), "Calling application must have a foreground activity or a foreground service"); // If it's a pin shortcut request, and there's already a shortcut with the same ID @@ -2301,247 +2373,327 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public void disableShortcuts(String packageName, List shortcutIds, + public AndroidFuture disableShortcuts(String packageName, List shortcutIds, CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) { - verifyCaller(packageName, userId); - Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); - - List changedShortcuts = null; - List removedShortcuts = null; - - synchronized (mLock) { - throwIfUserLockedL(userId); - - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - - ps.ensureImmutableShortcutsNotIncludedWithIds((List) shortcutIds, - /*ignoreInvisible=*/ true); - - final String disabledMessageString = - (disabledMessage == null) ? null : disabledMessage.toString(); - - for (int i = shortcutIds.size() - 1; i >= 0; i--) { - final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); - if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { - continue; - } - - final ShortcutInfo deleted = ps.disableWithId(id, - disabledMessageString, disabledMessageResId, - /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true, - ShortcutInfo.DISABLED_REASON_BY_APP); - - if (deleted == null) { - if (changedShortcuts == null) { - changedShortcuts = new ArrayList<>(1); - } - changedShortcuts.add(ps.findShortcutById(id)); - } else { - if (removedShortcuts == null) { - removedShortcuts = new ArrayList<>(1); - } - removedShortcuts.add(deleted); - } - } - - // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. - ps.adjustRanks(); + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); + List changedShortcuts = null; + List removedShortcuts = null; - packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + synchronized (mLock) { + throwIfUserLockedL(userId); - verifyStates(); + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); + + ps.ensureImmutableShortcutsNotIncludedWithIds((List) shortcutIds, + /*ignoreInvisible=*/ true); + + final String disabledMessageString = + (disabledMessage == null) ? null : disabledMessage.toString(); + + for (int i = shortcutIds.size() - 1; i >= 0; i--) { + final String id = Preconditions.checkStringNotEmpty( + (String) shortcutIds.get(i)); + if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { + continue; + } + + final ShortcutInfo deleted = ps.disableWithId(id, + disabledMessageString, disabledMessageResId, + /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true, + ShortcutInfo.DISABLED_REASON_BY_APP); + + if (deleted == null) { + if (changedShortcuts == null) { + changedShortcuts = new ArrayList<>(1); + } + changedShortcuts.add(ps.findShortcutById(id)); + } else { + if (removedShortcuts == null) { + removedShortcuts = new ArrayList<>(1); + } + removedShortcuts.add(deleted); + } + } + + // We may have removed dynamic shortcuts which may have left a gap, + // so adjust the ranks. + ps.adjustRanks(); + } + + packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + + verifyStates(); + + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } @Override - public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) { - verifyCaller(packageName, userId); - Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); - - List changedShortcuts = null; - - 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)); - if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { - continue; - } - ps.enableWithId(id); - - if (changedShortcuts == null) { - changedShortcuts = new ArrayList<>(1); - } - changedShortcuts.add(ps.findShortcutById(id)); - } + public AndroidFuture enableShortcuts( + String packageName, List shortcutIds, @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); + List changedShortcuts = null; - packageShortcutsChanged(packageName, userId, changedShortcuts, null); + synchronized (mLock) { + throwIfUserLockedL(userId); - verifyStates(); + 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)); + if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { + continue; + } + ps.enableWithId(id); + + if (changedShortcuts == null) { + changedShortcuts = new ArrayList<>(1); + } + changedShortcuts.add(ps.findShortcutById(id)); + } + } + + packageShortcutsChanged(packageName, userId, changedShortcuts, null); + + verifyStates(); + + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } @Override - public void removeDynamicShortcuts(String packageName, List shortcutIds, + public AndroidFuture removeDynamicShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) { - verifyCaller(packageName, userId); - Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; + } + injectPostToHandlerIfAppSearch(() -> { + try { + Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); + List changedShortcuts = null; + List removedShortcuts = null; - List changedShortcuts = null; - List removedShortcuts = null; + synchronized (mLock) { + throwIfUserLockedL(userId); - synchronized (mLock) { - throwIfUserLockedL(userId); + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + ps.ensureImmutableShortcutsNotIncludedWithIds((List) shortcutIds, + /*ignoreInvisible=*/ true); - ps.ensureImmutableShortcutsNotIncludedWithIds((List) shortcutIds, - /*ignoreInvisible=*/ true); + for (int i = shortcutIds.size() - 1; i >= 0; i--) { + final String id = Preconditions.checkStringNotEmpty( + (String) shortcutIds.get(i)); + if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { + continue; + } - for (int i = shortcutIds.size() - 1; i >= 0; i--) { - final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); - if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { - continue; - } - - ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true); - if (removed == null) { - if (changedShortcuts == null) { - changedShortcuts = new ArrayList<>(1); + ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ + true); + if (removed == null) { + if (changedShortcuts == null) { + changedShortcuts = new ArrayList<>(1); + } + changedShortcuts.add(ps.findShortcutById(id)); + } else { + if (removedShortcuts == null) { + removedShortcuts = new ArrayList<>(1); + } + removedShortcuts.add(removed); + } } - changedShortcuts.add(ps.findShortcutById(id)); - } else { - if (removedShortcuts == null) { - removedShortcuts = new ArrayList<>(1); - } - removedShortcuts.add(removed); + + // We may have removed dynamic shortcuts which may have left a gap, + // so adjust the ranks. + ps.adjustRanks(); } + packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + + verifyStates(); + + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); } - - // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. - ps.adjustRanks(); - } - packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); - - verifyStates(); + }); + return ret; } @Override - public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { - verifyCaller(packageName, userId); - - List changedShortcuts = new ArrayList<>(); - List removedShortcuts = null; - - synchronized (mLock) { - throwIfUserLockedL(userId); - - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - - // Dynamic shortcuts that are either cached or pinned will not get deleted. - ps.findAll(changedShortcuts, AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, - (ShortcutInfo si) -> si.isVisibleToPublisher() - && si.isDynamic() && (si.isCached() || si.isPinned()), - ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); - - removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); - changedShortcuts = prepareChangedShortcuts( - changedShortcuts, null, removedShortcuts, ps); + public AndroidFuture removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + List changedShortcuts = new ArrayList<>(); + List removedShortcuts = null; - packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + synchronized (mLock) { + throwIfUserLockedL(userId); - verifyStates(); + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); + + // Dynamic shortcuts that are either cached or pinned will not get deleted. + ps.findAll(changedShortcuts, + AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, + (ShortcutInfo si) -> si.isVisibleToPublisher() + && si.isDynamic() && (si.isCached() || si.isPinned()), + ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); + + removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); + changedShortcuts = prepareChangedShortcuts( + changedShortcuts, null, removedShortcuts, ps); + } + + packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + + verifyStates(); + + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } @Override - public void removeLongLivedShortcuts(String packageName, List shortcutIds, + public AndroidFuture removeLongLivedShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) { - verifyCaller(packageName, userId); - Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); - - List changedShortcuts = null; - List removedShortcuts = null; - - 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)); - if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { - continue; - } - - ShortcutInfo removed = ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); - if (removed != null) { - if (removedShortcuts == null) { - removedShortcuts = new ArrayList<>(1); - } - removedShortcuts.add(removed); - } else { - if (changedShortcuts == null) { - changedShortcuts = new ArrayList<>(1); - } - changedShortcuts.add(ps.findShortcutById(id)); - } - } - - // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. - ps.adjustRanks(); + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } - packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + injectPostToHandlerIfAppSearch(() -> { + try { + Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); + List changedShortcuts = null; + List removedShortcuts = null; - verifyStates(); + 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)); + if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { + continue; + } + + ShortcutInfo removed = ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ + true); + if (removed != null) { + if (removedShortcuts == null) { + removedShortcuts = new ArrayList<>(1); + } + removedShortcuts.add(removed); + } else { + if (changedShortcuts == null) { + changedShortcuts = new ArrayList<>(1); + } + changedShortcuts.add(ps.findShortcutById(id)); + } + } + + // We may have removed dynamic shortcuts which may have left a gap, + // so adjust the ranks. + ps.adjustRanks(); + } + packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + + verifyStates(); + + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } @Override - public void getShortcuts(String packageName, - @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId, - AndroidFuture> callback) { - try { - 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_ALL : 0); - - final String query = AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER + " " - + createQuery(matchDynamic, matchPinned, matchManifest, matchCached); - - callback.complete(getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, query, - (ShortcutInfo si) -> - si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0)); - } - } catch (Exception e) { - callback.completeExceptionally(e); + public AndroidFuture getShortcuts(String packageName, + @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + 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_ALL : 0); + + final String query = AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER + " " + + createQuery(matchDynamic, matchPinned, matchManifest, matchCached); + + ret.complete(getShortcutsWithQueryLocked( + packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, query, + (ShortcutInfo si) -> + si.isVisibleToPublisher() + && (si.getFlags() & shortcutFlags) != 0)); + } + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } @Override - public void getShareTargets(String packageName, IntentFilter filter, @UserIdInt int userId, - AndroidFuture callback) { + public AndroidFuture getShareTargets( + String packageName, IntentFilter filter, @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); try { Preconditions.checkStringNotEmpty(packageName, "packageName"); Objects.requireNonNull(filter, "intentFilter"); @@ -2549,21 +2701,29 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, "getShareTargets"); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - final List shortcutInfoList = new ArrayList<>(); - - final ShortcutUser user = getUserShortcutsLocked(userId); - user.forAllPackages( - p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter))); - - callback.complete(new ParceledListSlice<>(shortcutInfoList)); - } } catch (Exception e) { - callback.completeExceptionally(e); + ret.completeExceptionally(e); + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + synchronized (mLock) { + throwIfUserLockedL(userId); + + final List shortcutInfoList = + new ArrayList<>(); + + final ShortcutUser user = getUserShortcutsLocked(userId); + user.forAllPackages( + p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter))); + + ret.complete(new ParceledListSlice<>(shortcutInfoList)); + } + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } @Override @@ -2660,34 +2820,48 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public void reportShortcutUsed(String packageName, String shortcutId, int userId) { - verifyCaller(packageName, userId); - - Objects.requireNonNull(shortcutId); - - if (DEBUG) { - Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", - shortcutId, packageName, userId)); + public AndroidFuture reportShortcutUsed(String packageName, String shortcutId, int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + if (!verifyCaller(packageName, userId, ret)) { + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + Objects.requireNonNull(shortcutId); - synchronized (mLock) { - throwIfUserLockedL(userId); + if (DEBUG) { + Slog.d(TAG, String.format( + "reportShortcutUsed: Shortcut %s package %s used on user %d", + shortcutId, packageName, userId)); + } - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + synchronized (mLock) { + throwIfUserLockedL(userId); - if (ps.findShortcutById(shortcutId) == null) { - Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", - packageName, shortcutId)); - return; + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); + + if (ps.findShortcutById(shortcutId) == null) { + Log.w(TAG, String.format( + "reportShortcutUsed: package %s doesn't have shortcut %s", + packageName, shortcutId)); + ret.complete(false); + return; + } + } + + final long token = injectClearCallingIdentity(); + try { + mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); + } finally { + injectRestoreCallingIdentity(token); + } + ret.complete(true); + } catch (Exception e) { + ret.completeExceptionally(e); } - } - - final long token = injectClearCallingIdentity(); - try { - mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); - } finally { - injectRestoreCallingIdentity(token); - } + }); + return ret; } @Override @@ -2734,22 +2908,36 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public void onApplicationActive(String packageName, int userId) { + public AndroidFuture onApplicationActive(String packageName, int userId) { + final AndroidFuture ret = new AndroidFuture<>(); if (DEBUG) { Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); } - enforceResetThrottlingPermission(); - - synchronized (mLock) { - if (!isUserUnlockedL(userId)) { - // This is called by system UI, so no need to throw. Just ignore. - return; - } - - getPackageShortcutsLocked(packageName, userId) - .resetRateLimitingForCommandLineNoSaving(); - saveUserLocked(userId); + try { + enforceResetThrottlingPermission(); + } catch (Exception e) { + ret.completeExceptionally(e); + return ret; } + injectPostToHandlerIfAppSearch(() -> { + try { + synchronized (mLock) { + if (!isUserUnlockedL(userId)) { + // This is called by system UI, so no need to throw. Just ignore. + ret.complete(null); + return; + } + + getPackageShortcutsLocked(packageName, userId) + .resetRateLimitingForCommandLineNoSaving(); + saveUserLocked(userId); + } + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } // We override this method in unit tests to do a simpler check. @@ -3203,9 +3391,8 @@ public class ShortcutService extends IShortcutService.Stub { @Override public List getShareTargets( @NonNull String callingPackage, @NonNull IntentFilter intentFilter, int userId) { - final AndroidFuture future = new AndroidFuture<>(); - ShortcutService.this.getShareTargets( - callingPackage, intentFilter, userId, future); + final AndroidFuture future = ShortcutService.this.getShareTargets( + callingPackage, intentFilter, userId); try { return future.get().getList(); } catch (InterruptedException | ExecutionException e) { @@ -4331,56 +4518,72 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public void applyRestore(byte[] payload, @UserIdInt int userId) { - enforceSystem(); - if (DEBUG || DEBUG_REBOOT) { - Slog.d(TAG, "Restoring user " + userId); + public AndroidFuture applyRestore(byte[] payload, @UserIdInt int userId) { + final AndroidFuture ret = new AndroidFuture<>(); + try { + enforceSystem(); + } catch (Exception e) { + ret.completeExceptionally(e); + return ret; } - synchronized (mLock) { - if (!isUserUnlockedL(userId)) { - wtf("Can't restore: user " + userId + " is locked or not running"); - return; - } - - // Note we print the file timestamps in dumpsys too, but also printing the timestamp - // in the files anyway. - mShortcutDumpFiles.save("restore-0-start.txt", pw -> { - pw.print("Start time: "); - dumpCurrentTime(pw); - pw.println(); - }); - mShortcutDumpFiles.save("restore-1-payload.xml", payload); - - // Actually do restore. - final ShortcutUser restored; - final ByteArrayInputStream is = new ByteArrayInputStream(payload); + injectPostToHandler(() -> { try { - restored = loadUserInternal(userId, is, /* fromBackup */ true); - } catch (XmlPullParserException | IOException | InvalidFileFormatException e) { - Slog.w(TAG, "Restoration failed.", e); - return; - } - mShortcutDumpFiles.save("restore-2.txt", this::dumpInner); + if (DEBUG || DEBUG_REBOOT) { + Slog.d(TAG, "Restoring user " + userId); + } + synchronized (mLock) { + if (!isUserUnlockedL(userId)) { + wtf("Can't restore: user " + userId + " is locked or not running"); + ret.complete(null); + return; + } - getUserShortcutsLocked(userId).mergeRestoredFile(restored); + // Note we print the file timestamps in dumpsys too, but also printing the + // timestamp in the files anyway. + mShortcutDumpFiles.save("restore-0-start.txt", pw -> { + pw.print("Start time: "); + dumpCurrentTime(pw); + pw.println(); + }); + mShortcutDumpFiles.save("restore-1-payload.xml", payload); - mShortcutDumpFiles.save("restore-3.txt", this::dumpInner); + // Actually do restore. + final ShortcutUser restored; + final ByteArrayInputStream is = new ByteArrayInputStream(payload); + try { + restored = loadUserInternal(userId, is, /* fromBackup */ true); + } catch (XmlPullParserException | IOException | InvalidFileFormatException e) { + Slog.w(TAG, "Restoration failed.", e); + ret.complete(null); + return; + } + mShortcutDumpFiles.save("restore-2.txt", this::dumpInner); - // Rescan all packages to re-publish manifest shortcuts and do other checks. - rescanUpdatedPackagesLocked(userId, - 0 // lastScanTime = 0; rescan all packages. + getUserShortcutsLocked(userId).mergeRestoredFile(restored); + + mShortcutDumpFiles.save("restore-3.txt", this::dumpInner); + + // Rescan all packages to re-publish manifest shortcuts and do other checks. + rescanUpdatedPackagesLocked(userId, + 0 // lastScanTime = 0; rescan all packages. ); - mShortcutDumpFiles.save("restore-4.txt", this::dumpInner); + mShortcutDumpFiles.save("restore-4.txt", this::dumpInner); - mShortcutDumpFiles.save("restore-5-finish.txt", pw -> { - pw.print("Finish time: "); - dumpCurrentTime(pw); - pw.println(); - }); + mShortcutDumpFiles.save("restore-5-finish.txt", pw -> { + pw.print("Finish time: "); + dumpCurrentTime(pw); + pw.println(); + }); - saveUserLocked(userId); - } + saveUserLocked(userId); + } + ret.complete(null); + } catch (Exception e) { + ret.completeExceptionally(e); + } + }); + return ret; } // === Dump ===