Merge "Integrate LauncherApps API with AppSearch"

This commit is contained in:
TreeHugger Robot
2022-01-06 07:40:19 +00:00
committed by Android (Google) Code Review
8 changed files with 417 additions and 95 deletions

View File

@@ -2918,6 +2918,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
}

View File

@@ -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<List<ShortcutInfo>> cb);
void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
in UserHandle user);
boolean startShortcut(String callingPackage, String packageName, String featureId, String id,

View File

@@ -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.
*
* <p>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<ShortcutInfo> getShortcutsBlocked(@NonNull ShortcutQuery query,
@NonNull UserHandle user) {
logErrorForInvalidProfileAccess(user);
final AndroidFuture<List<ShortcutInfo>> 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.
*/

View File

@@ -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<LocusId> 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<String> shortcutIds,
@Nullable List<LocusId> locusIds, @Nullable ComponentName componentName,
@ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid,
AndroidFuture<List<ShortcutInfo>> 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<Intent[]> 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<ParcelFileDescriptor> 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<String> cb);
public abstract boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId,
@NonNull IntentFilter filter);

View File

@@ -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<Intent[]> 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<List<ShortcutInfo>> 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<String> shortcutIds = query.getShortcutIds();
final List<LocusId> 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<ParcelFileDescriptor> 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<String> 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<Intent[]> 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;
}

View File

@@ -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<ShortcutInfo> result,
@NonNull final Collection<String> ids, @Nullable final Predicate<ShortcutInfo> 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<ShortcutInfo> result,
@NonNull final Collection<String> ids, @Nullable final Predicate<ShortcutInfo> 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<String> 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<ShortcutInfo> result,
@Nullable final Predicate<ShortcutInfo> query, final int cloneFlag,
@Nullable final String callingLauncher,
@@ -2411,6 +2376,25 @@ class ShortcutPackage extends ShortcutPackageItem {
})));
}
void getShortcutByIdsAsync(@NonNull final Set<String> ids,
@NonNull final Consumer<List<ShortcutInfo>> 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<ShortcutInfo> 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()) {

View File

@@ -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<ShortcutInfo> 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<ShortcutInfo> getFilterFromQuery(@Nullable ArraySet<String> 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<String> shortcutIds,
@Nullable List<LocusId> locusIds, @Nullable ComponentName componentName,
int queryFlags, int userId, int callingPid, int callingUid,
@NonNull AndroidFuture<List<ShortcutInfo>> cb) {
final List<ShortcutInfo> 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<String> 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<ShortcutInfo> 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<ShortcutInfo> 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<Intent[]> 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<ParcelFileDescriptor> 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<String> 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<ShortcutInfo> 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;
}

View File

@@ -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<ShortcutInfo> getAllPersistedShortcuts() {
try {
SystemClock.sleep(500);
SystemClock.sleep(5000);
final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
getPersistedShortcut(future);
return future.get(10, TimeUnit.SECONDS);