From 24912ef392e9d3331c917959331f1c43e0da2190 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Tue, 9 Nov 2021 01:19:05 +0000 Subject: [PATCH] Integrate LauncherApps API with AppSearch - getShortcutIntent and startShortcut now additionally checks AppSearch if specifed shortcuts cannot be found in memory. - getShortcutIconFd and getShortcutIconUri now additionally checks AppSearch if specified shortcuts cannot be found in memory. - getShortcutIconResId is unchanged since the api is deprecated. - Introduced ShortcutQuery.FLAG_GET_PERSISTED_DATA, when used in getShortcuts, it additionally performs the check in AppSearch. Bug: 151359749 Test: atest CtsShortcutManagerTestCases Change-Id: I0e56e3d0261fc0a3cf0ab4c5decbbd59ac3d7d29 --- core/api/system-current.txt | 1 + .../android/content/pm/ILauncherApps.aidl | 4 + .../java/android/content/pm/LauncherApps.java | 37 ++- .../content/pm/ShortcutServiceInternal.java | 38 +++ .../server/pm/LauncherAppsService.java | 82 ++++- .../android/server/pm/ShortcutPackage.java | 61 ++-- .../android/server/pm/ShortcutService.java | 282 +++++++++++++++--- .../server/pm/ShortcutManagerTest12.java | 7 +- 8 files changed, 417 insertions(+), 95 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a33d0a2c08795..1d551f3234b45 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2839,6 +2839,7 @@ package android.content.pm { } public static class LauncherApps.ShortcutQuery { + field public static final int FLAG_GET_PERSISTED_DATA = 4096; // 0x1000 field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800 } diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 37fd3ffdeafa5..cb8988eb5b92f 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -38,6 +38,8 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.ParcelFileDescriptor; +import com.android.internal.infra.AndroidFuture; + import java.util.List; /** @@ -73,6 +75,8 @@ interface ILauncherApps { ParceledListSlice getShortcuts(String callingPackage, in ShortcutQueryWrapper query, in UserHandle user); + void getShortcutsAsync(String callingPackage, in ShortcutQueryWrapper query, + in UserHandle user, in AndroidFuture> cb); void pinShortcuts(String callingPackage, String packageName, in List shortcutIds, in UserHandle user); boolean startShortcut(String callingPackage, String packageName, String featureId, String id, diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 617d3ab335e90..a0d348f1cbd50 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -69,6 +69,7 @@ import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileNotFoundException; @@ -84,6 +85,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; /** @@ -439,6 +441,17 @@ public class LauncherApps { */ public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2; + /** + * Includes shortcuts from persistence layer in the search result. + * + *

The caller should make the query on a worker thread since accessing persistence layer + * is considered asynchronous. + * + * @hide + */ + @SystemApi + public static final int FLAG_GET_PERSISTED_DATA = 1 << 12; + /** * Populate the persons field in the result. See {@link ShortcutInfo#getPersons()}. * @@ -459,6 +472,7 @@ public class LauncherApps { FLAG_MATCH_PINNED_BY_ANY_LAUNCHER, FLAG_GET_KEY_FIELDS_ONLY, FLAG_GET_PERSONS_DATA, + FLAG_GET_PERSISTED_DATA }) @Retention(RetentionPolicy.SOURCE) public @interface QueryFlags {} @@ -1137,6 +1151,9 @@ public class LauncherApps { @NonNull UserHandle user) { logErrorForInvalidProfileAccess(user); try { + if ((query.mQueryFlags & ShortcutQuery.FLAG_GET_PERSISTED_DATA) != 0) { + return getShortcutsBlocked(query, user); + } // Note this is the only case we need to update the disabled message for shortcuts // that weren't restored. // The restore problem messages are only shown by the user, and publishers will never @@ -1144,13 +1161,29 @@ public class LauncherApps { // changed callback, but that only returns shortcuts with the "key" information, so // that won't return disabled message. return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(), - new ShortcutQueryWrapper(query), user) - .getList()); + new ShortcutQueryWrapper(query), user) + .getList()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + private List getShortcutsBlocked(@NonNull ShortcutQuery query, + @NonNull UserHandle user) { + logErrorForInvalidProfileAccess(user); + final AndroidFuture> future = new AndroidFuture<>(); + future.thenApply(this::maybeUpdateDisabledMessage); + try { + mService.getShortcutsAsync(mContext.getPackageName(), + new ShortcutQueryWrapper(query), user, future); + return future.get(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + /** * @hide // No longer used. Use getShortcuts() instead. Kept for unit tests. */ diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 3ed5c6457fa59..087a7952acd70 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -29,6 +29,8 @@ import android.content.pm.LauncherApps.ShortcutQuery; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import com.android.internal.infra.AndroidFuture; + import java.util.List; /** @@ -50,6 +52,19 @@ public abstract class ShortcutServiceInternal { @Nullable List locusIds, @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid); + /** + * Retrieves shortcuts asynchronously. Query will go through persistence layer (thus making the + * call async) if querying by shortcutIds in a specific package; otherwise it's effectively the + * same as calling {@link #getShortcuts}. + */ + public abstract void + getShortcutsAsync(int launcherUserId, + @NonNull String callingPackage, long changedSince, + @Nullable String packageName, @Nullable List shortcutIds, + @Nullable List locusIds, @Nullable ComponentName componentName, + @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid, + AndroidFuture> cb); + public abstract boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String id, int userId); @@ -63,6 +78,14 @@ public abstract class ShortcutServiceInternal { @NonNull String packageName, @NonNull String shortcutId, int userId, int callingPid, int callingUid); + /** + * Retrieves the intents from a specified shortcut asynchronously. + */ + public abstract void createShortcutIntentsAsync( + int launcherUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + int callingPid, int callingUid, @NonNull AndroidFuture cb); + public abstract void addListener(@NonNull ShortcutChangeListener listener); public abstract void addShortcutChangeCallback( @@ -82,6 +105,13 @@ public abstract class ShortcutServiceInternal { @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); + /** + * Retrieves a file descriptor from the icon in a specified shortcut asynchronously. + */ + public abstract void getShortcutIconFdAsync(int launcherUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull AndroidFuture cb); + public abstract boolean hasShortcutHostPermission(int launcherUserId, @NonNull String callingPackage, int callingPid, int callingUid); @@ -117,6 +147,14 @@ public abstract class ShortcutServiceInternal { public abstract String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); + /** + * Retrieves the icon Uri of the shortcut asynchronously, and grants Uri read permission to the + * caller. + */ + public abstract void getShortcutIconUriAsync(int launcherUserId, + @NonNull String launcherPackage, @NonNull String packageName, + @NonNull String shortcutId, int userId, @NonNull AndroidFuture cb); + public abstract boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, @NonNull IntentFilter filter); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 6e6773feb5f82..c6756353a5ea1 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -88,6 +88,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; +import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -103,6 +104,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.ExecutionException; /** * Service that manages requests and callbacks for launchers that support @@ -728,9 +730,16 @@ public class LauncherAppsService extends SystemService { return null; } - final Intent[] intents = mShortcutServiceInternal.createShortcutIntents( - getCallingUserId(), callingPackage, packageName, shortcutId, - user.getIdentifier(), injectBinderCallingPid(), injectBinderCallingUid()); + final AndroidFuture ret = new AndroidFuture<>(); + Intent[] intents; + mShortcutServiceInternal.createShortcutIntentsAsync(getCallingUserId(), + callingPackage, packageName, shortcutId, user.getIdentifier(), + injectBinderCallingPid(), injectBinderCallingUid(), ret); + try { + intents = ret.get(); + } catch (InterruptedException | ExecutionException e) { + return null; + } if (intents == null || intents.length == 0) { return null; } @@ -900,6 +909,40 @@ public class LauncherAppsService extends SystemService { injectBinderCallingPid(), injectBinderCallingUid())); } + @Override + public void getShortcutsAsync(@NonNull final String callingPackage, + @NonNull final ShortcutQueryWrapper query, @NonNull final UserHandle targetUser, + @NonNull final AndroidFuture> cb) { + ensureShortcutPermission(callingPackage); + if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) { + cb.complete(Collections.EMPTY_LIST); + return; + } + + final long changedSince = query.getChangedSince(); + final String packageName = query.getPackage(); + final List shortcutIds = query.getShortcutIds(); + final List locusIds = query.getLocusIds(); + final ComponentName componentName = query.getActivity(); + final int flags = query.getQueryFlags(); + if (shortcutIds != null && packageName == null) { + throw new IllegalArgumentException( + "To query by shortcut ID, package name must also be set"); + } + if (locusIds != null && packageName == null) { + throw new IllegalArgumentException( + "To query by locus ID, package name must also be set"); + } + if ((query.getQueryFlags() & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { + ensureStrictAccessShortcutsPermission(callingPackage); + } + + mShortcutServiceInternal.getShortcutsAsync(getCallingUserId(), + callingPackage, changedSince, packageName, shortcutIds, locusIds, + componentName, flags, targetUser.getIdentifier(), + injectBinderCallingPid(), injectBinderCallingUid(), cb); + } + @Override public void registerShortcutChangeCallback(@NonNull final String callingPackage, @NonNull final ShortcutQueryWrapper query, @@ -991,8 +1034,14 @@ public class LauncherAppsService extends SystemService { return null; } - return mShortcutServiceInternal.getShortcutIconFd(getCallingUserId(), - callingPackage, packageName, id, targetUserId); + final AndroidFuture ret = new AndroidFuture<>(); + mShortcutServiceInternal.getShortcutIconFdAsync(getCallingUserId(), + callingPackage, packageName, id, targetUserId, ret); + try { + return ret.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } @Override @@ -1003,8 +1052,14 @@ public class LauncherAppsService extends SystemService { return null; } - return mShortcutServiceInternal.getShortcutIconUri(getCallingUserId(), callingPackage, - packageName, shortcutId, userId); + final AndroidFuture ret = new AndroidFuture<>(); + mShortcutServiceInternal.getShortcutIconUriAsync(getCallingUserId(), callingPackage, + packageName, shortcutId, userId, ret); + try { + return ret.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } @Override @@ -1037,9 +1092,16 @@ public class LauncherAppsService extends SystemService { ensureShortcutPermission(callerUid, callerPid, callingPackage); } - final Intent[] intents = mShortcutServiceInternal.createShortcutIntents( - callingUserId, callingPackage, packageName, shortcutId, targetUserId, - callerPid, callerUid); + final AndroidFuture ret = new AndroidFuture<>(); + Intent[] intents; + mShortcutServiceInternal.createShortcutIntentsAsync(getCallingUserId(), callingPackage, + packageName, shortcutId, targetUserId, + injectBinderCallingPid(), injectBinderCallingUid(), ret); + try { + intents = ret.get(); + } catch (InterruptedException | ExecutionException e) { + return false; + } if (intents == null || intents.length == 0) { return false; } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 3d10b6f06488b..bf7ef1b247764 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -22,6 +22,7 @@ import android.app.Person; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSession; +import android.app.appsearch.GetByDocumentIdRequest; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.PutDocumentsRequest; import android.app.appsearch.RemoveByDocumentIdRequest; @@ -820,42 +821,6 @@ class ShortcutPackage extends ShortcutPackageItem { getPinnedByAnyLauncher, si)); } - /** - * Find all shortcuts that has id matching {@code ids}. - */ - public void findAllByIds(@NonNull final List result, - @NonNull final Collection ids, @Nullable final Predicate filter, - final int cloneFlag) { - findAllByIds(result, ids, filter, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false); - } - - /** - * Find all shortcuts that has id matching {@code ids}. - * - * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned - * by the calling launcher will not be included in the result, and also "isPinned" will be - * adjusted for the caller too. - */ - public void findAllByIds(@NonNull List result, - @NonNull final Collection ids, @Nullable final Predicate query, - int cloneFlag, @Nullable String callingLauncher, int launcherUserId, - boolean getPinnedByAnyLauncher) { - if (getPackageInfo().isShadow()) { - // Restored and the app not installed yet, so don't return any. - return; - } - final ShortcutService s = mShortcutUser.mService; - - // Set of pinned shortcuts by the calling launcher. - final ArraySet pinnedByCallerSet = (callingLauncher == null) ? null - : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) - .getPinnedShortcutIds(getPackageName(), getPackageUserId()); - for (ShortcutInfo si : mShortcuts.values()) { - filter(result, query, cloneFlag, callingLauncher, pinnedByCallerSet, - getPinnedByAnyLauncher, si); - } - } - private void filter(@NonNull final List result, @Nullable final Predicate query, final int cloneFlag, @Nullable final String callingLauncher, @@ -2411,6 +2376,25 @@ class ShortcutPackage extends ShortcutPackageItem { }))); } + void getShortcutByIdsAsync(@NonNull final Set ids, + @NonNull final Consumer> cb) { + if (!isAppSearchEnabled()) { + cb.accept(Collections.emptyList()); + return; + } + runAsSystem(() -> fromAppSearch().thenAccept(session -> { + session.getByDocumentId(new GetByDocumentIdRequest.Builder(getPackageName()) + .addIds(ids).build(), mShortcutUser.mExecutor, result -> { + final List ret = result.getSuccesses().values() + .stream().map(doc -> + new AppSearchShortcutInfo(doc) + .toShortcutInfo(mShortcutUser.getUserId())) + .collect(Collectors.toList()); + cb.accept(ret); + }); + })); + } + private void removeShortcutAsync(@NonNull final String... id) { Objects.requireNonNull(id); removeShortcutAsync(Arrays.asList(id)); @@ -2444,9 +2428,8 @@ class ShortcutPackage extends ShortcutPackageItem { } if (ShortcutService.DEBUG_REBOOT) { Slog.d(TAG, "Saving shortcuts async for user=" + mShortcutUser.getUserId() - + " pkg=" + getPackageName() + " ids=[" - + shortcuts.stream().map(ShortcutInfo::getId) - .collect(Collectors.joining(",")) + "]"); + + " pkg=" + getPackageName() + " ids=" + shortcuts.stream() + .map(ShortcutInfo::getId).collect(Collectors.joining(",", "[", "]"))); } runAsSystem(() -> fromAppSearch().thenAccept(session -> { if (shortcuts.isEmpty()) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index a482f9a619ba3..0a2735cdbf760 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -153,6 +153,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * TODO: @@ -2957,13 +2958,8 @@ public class ShortcutService extends IShortcutService.Stub { final Predicate filter = getFilterFromQuery(ids, locusIds, changedSince, componentName, queryFlags, getPinnedByAnyLauncher); - if (ids != null && !ids.isEmpty()) { - p.findAllByIds(ret, ids, filter, cloneFlag, callingPackage, launcherUserId, + p.findAll(ret, filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); - } else { - p.findAll(ret, filter, cloneFlag, callingPackage, launcherUserId, - getPinnedByAnyLauncher); - } } private Predicate getFilterFromQuery(@Nullable ArraySet ids, @@ -3009,6 +3005,51 @@ public class ShortcutService extends IShortcutService.Stub { }; } + @Override + public void getShortcutsAsync(int launcherUserId, + @NonNull String callingPackage, long changedSince, + @Nullable String packageName, @Nullable List shortcutIds, + @Nullable List locusIds, @Nullable ComponentName componentName, + int queryFlags, int userId, int callingPid, int callingUid, + @NonNull AndroidFuture> cb) { + final List ret = getShortcuts(launcherUserId, callingPackage, + changedSince, packageName, shortcutIds, locusIds, componentName, queryFlags, + userId, callingPid, callingUid); + if (shortcutIds == null || packageName == null || ret.size() >= shortcutIds.size()) { + // skip persistence layer if not querying by id in a specific package or all + // shortcuts have already been found. + cb.complete(ret); + return; + } + final ShortcutPackage p; + synchronized (mLock) { + p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); + } + if (p == null) { + cb.complete(ret); + return; // Bail-out directly if package doesn't exist. + } + // fetch remaining shortcuts from persistence layer + final ArraySet ids = new ArraySet<>(shortcutIds); + // remove the ids that are already fetched + ret.stream().map(ShortcutInfo::getId).collect(Collectors.toList()).forEach(ids::remove); + + int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; + if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) { + flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; + } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { + flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON; + } + final int cloneFlag = flags; + + p.getShortcutByIdsAsync(ids, shortcuts -> { + if (shortcuts != null) { + shortcuts.stream().map(si -> si.clone(cloneFlag)).forEach(ret::add); + } + cb.complete(ret); + }); + } + @Override public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId) { @@ -3047,12 +3088,32 @@ public class ShortcutService extends IShortcutService.Stub { } final ArrayList list = new ArrayList<>(1); - p.findAllByIds(list, Collections.singletonList(shortcutId), - (ShortcutInfo si) -> shortcutId.equals(si.getId()), + p.findAll(list, (ShortcutInfo si) -> shortcutId.equals(si.getId()), /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher); return list.size() == 0 ? null : list.get(0); } + private void getShortcutInfoAsync( + int launcherUserId, @NonNull String packageName, @NonNull String shortcutId, + int userId, @NonNull Consumer cb) { + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); + + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + final ShortcutPackage p; + synchronized (mLock) { + p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); + } + if (p == null) { + cb.accept(null); + return; + } + p.getShortcutByIdsAsync(Collections.singleton(shortcutId), shortcuts -> + cb.accept(shortcuts == null || shortcuts.isEmpty() ? null : shortcuts.get(0))); + } + @Override public void pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @@ -3236,6 +3297,48 @@ public class ShortcutService extends IShortcutService.Stub { } } + @Override + public void createShortcutIntentsAsync(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull String shortcutId, int userId, int callingPid, + int callingUid, @NonNull AndroidFuture cb) { + // Calling permission must be checked by LauncherAppsImpl. + Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); + Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); + + // Check in memory shortcut first + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(); + + final boolean getPinnedByAnyLauncher = + canSeeAnyPinnedShortcut(callingPackage, launcherUserId, + callingPid, callingUid); + + // Make sure the shortcut is actually visible to the launcher. + final ShortcutInfo si = getShortcutInfoLocked( + launcherUserId, callingPackage, packageName, shortcutId, userId, + getPinnedByAnyLauncher); + if (si != null) { + if (!si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) { + Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); + cb.complete(null); + return; + } + cb.complete(si.getIntents()); + return; + } + } + + // Otherwise check persisted shortcuts + getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { + cb.complete(si == null ? null : si.getIntents()); + }); + } + @Override public void addListener(@NonNull ShortcutChangeListener listener) { synchronized (mLock) { @@ -3326,23 +3429,68 @@ public class ShortcutService extends IShortcutService.Stub { } final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); - if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { + if (shortcutInfo == null) { return null; } - final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); - if (path == null) { - Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); - return null; + return getShortcutIconParcelFileDescriptor(shortcutInfo); + } + } + + @Override + public void getShortcutIconFdAsync(int launcherUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull AndroidFuture cb) { + Objects.requireNonNull(callingPackage, "callingPackage"); + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(shortcutId, "shortcutId"); + + // Checks shortcuts in memory first + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(); + + final ShortcutPackage p = getUserShortcutsLocked(userId) + .getPackageShortcutsIfExists(packageName); + if (p == null) { + cb.complete(null); + return; } - try { - return ParcelFileDescriptor.open( - new File(path), - ParcelFileDescriptor.MODE_READ_ONLY); - } catch (FileNotFoundException e) { - Slog.e(TAG, "Icon file not found: " + path); - return null; + + final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); + if (shortcutInfo != null) { + cb.complete(getShortcutIconParcelFileDescriptor(shortcutInfo)); + return; } } + + // Otherwise check persisted shortcuts + getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { + cb.complete(getShortcutIconParcelFileDescriptor(si)); + }); + } + + @Nullable + private ParcelFileDescriptor getShortcutIconParcelFileDescriptor( + @NonNull final ShortcutInfo shortcutInfo) { + if (!shortcutInfo.hasIconFile()) { + return null; + } + final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); + if (path == null) { + Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); + return null; + } + try { + return ParcelFileDescriptor.open( + new File(path), + ParcelFileDescriptor.MODE_READ_ONLY); + } catch (FileNotFoundException e) { + Slog.e(TAG, "Icon file not found: " + path); + return null; + } } @Override @@ -3366,34 +3514,82 @@ public class ShortcutService extends IShortcutService.Stub { } final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); - if (shortcutInfo == null || !shortcutInfo.hasIconUri()) { + if (shortcutInfo == null) { return null; } - String uri = shortcutInfo.getIconUri(); - if (uri == null) { - Slog.w(TAG, "null uri detected in getShortcutIconUri()"); - return null; + return getShortcutIconUriInternal(launcherUserId, launcherPackage, + packageName, shortcutInfo, userId); + } + } + + @Override + public void getShortcutIconUriAsync(int launcherUserId, @NonNull String launcherPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull AndroidFuture cb) { + Objects.requireNonNull(launcherPackage, "launcherPackage"); + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(shortcutId, "shortcutId"); + + // Checks shortcuts in memory first + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + getLauncherShortcutsLocked(launcherPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(); + + final ShortcutPackage p = getUserShortcutsLocked(userId) + .getPackageShortcutsIfExists(packageName); + if (p == null) { + cb.complete(null); + return; } - final long token = Binder.clearCallingIdentity(); - try { - int packageUid = mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); - // Grant read uri permission to the caller on behalf of the shortcut owner. All - // granted permissions are revoked when the default launcher changes, or when - // device is rebooted. - mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid, - launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, - userId, launcherUserId); - } catch (Exception e) { - Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri, - e); - uri = null; - } finally { - Binder.restoreCallingIdentity(token); + final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); + if (shortcutInfo != null) { + cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage, + packageName, shortcutInfo, userId)); + return; } - return uri; } + + // Otherwise check persisted shortcuts + getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { + cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage, + packageName, si, userId)); + }); + } + + private String getShortcutIconUriInternal(int launcherUserId, + @NonNull String launcherPackage, @NonNull String packageName, + @NonNull ShortcutInfo shortcutInfo, int userId) { + if (!shortcutInfo.hasIconUri()) { + return null; + } + String uri = shortcutInfo.getIconUri(); + if (uri == null) { + Slog.w(TAG, "null uri detected in getShortcutIconUri()"); + return null; + } + + final long token = Binder.clearCallingIdentity(); + try { + int packageUid = mPackageManagerInternal.getPackageUid(packageName, + PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); + // Grant read uri permission to the caller on behalf of the shortcut owner. All + // granted permissions are revoked when the default launcher changes, or when + // device is rebooted. + mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid, + launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, + userId, launcherUserId); + } catch (Exception e) { + Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri, + e); + uri = null; + } finally { + Binder.restoreCallingIdentity(token); + } + return uri; } @Override @@ -5154,7 +5350,7 @@ public class ShortcutService extends IShortcutService.Stub { } List result = new ArrayList<>(); - ps.findAllByIds(result, resultIds, (ShortcutInfo si) -> resultIds.contains(si.getId()), + ps.findAll(result, (ShortcutInfo si) -> resultIds.contains(si.getId()), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); return result; } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java index 0708be2fb0c38..78bcf0c692b8d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java @@ -147,6 +147,11 @@ public class ShortcutManagerTest12 extends BaseShortcutManagerTest { // Verifies pushDynamicShortcuts further persists shortcuts into AppSearch without // removing previous shortcuts when max number of shortcuts is reached. mManager.pushDynamicShortcut(makeShortcut("s6")); + // Increasing the max number of shortcuts since number of results per page in AppSearch + // is set to match the former. + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_MAX_SHORTCUTS + "=10," + + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); shortcuts = getAllPersistedShortcuts(); assertNotNull(shortcuts); assertEquals(6, shortcuts.size()); @@ -281,7 +286,7 @@ public class ShortcutManagerTest12 extends BaseShortcutManagerTest { private List getAllPersistedShortcuts() { try { - SystemClock.sleep(500); + SystemClock.sleep(5000); final AndroidFuture> future = new AndroidFuture<>(); getPersistedShortcut(future); return future.get(10, TimeUnit.SECONDS);