Merge "Integrate LauncherApps API with AppSearch"
This commit is contained in:
committed by
Android (Google) Code Review
commit
efa662ec64
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user