summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java749
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java14
4 files changed, 532 insertions, 255 deletions
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 19c56f82d7bf..cea2656c4a19 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -19,18 +19,33 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Person;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+import android.app.appsearch.ReportUsageRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.LocusId;
+import android.content.pm.AppSearchPerson;
+import android.content.pm.AppSearchShortcutInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.os.Binder;
import android.os.PersistableBundle;
+import android.os.StrictMode;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -43,8 +58,10 @@ import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.ShortcutService.DumpFilter;
@@ -64,13 +81,17 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -137,9 +158,9 @@ class ShortcutPackage extends ShortcutPackageItem {
private static final String KEY_BITMAP_BYTES = "bitmapBytes";
/**
- * All the shortcuts from the package, keyed on IDs.
+ * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
- private final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+ final ArraySet<ShortcutInfo> mShortcuts = new ArraySet<>();
/**
* All the share targets from the package
@@ -199,7 +220,9 @@ class ShortcutPackage extends ShortcutPackageItem {
}
public int getShortcutCount() {
- return mShortcuts.size();
+ final int[] count = new int[1];
+ forEachShortcut(si -> count[0]++);
+ return count[0];
}
@Override
@@ -213,17 +236,20 @@ class ShortcutPackage extends ShortcutPackageItem {
// - Unshadow all shortcuts.
// - Set disabled reason.
// - Disable if needed.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_SHADOW);
-
- shortcut.setDisabledReason(restoreBlockReason);
- if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
- shortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
- }
- });
- }
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasFlags(ShortcutInfo.FLAG_SHADOW)
+ && si.getDisabledReason() == restoreBlockReason
+ && restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ return false;
+ }
+ si.clearFlags(ShortcutInfo.FLAG_SHADOW);
+
+ si.setDisabledReason(restoreBlockReason);
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.addFlags(ShortcutInfo.FLAG_DISABLED);
+ }
+ return true;
+ });
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags();
@@ -234,7 +260,7 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
@Nullable
public ShortcutInfo findShortcutById(String id) {
- return mShortcuts.get(id);
+ return getShortcutById(id);
}
public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -300,8 +326,9 @@ class ShortcutPackage extends ShortcutPackageItem {
* Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
+ final ShortcutInfo shortcut = getShortcutById(id);
if (shortcut != null) {
+ removeShortcut(id);
mShortcutUser.mService.removeIconLocked(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
| ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
@@ -318,10 +345,10 @@ class ShortcutPackage extends ShortcutPackageItem {
forceDeleteShortcutInner(newShortcut.getId());
- // Extract Icon and update the icon res ID and the bitmap path.
s.saveIconAndFixUpShortcutLocked(newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
- mShortcuts.put(newShortcut.getId(), newShortcut);
+
+ saveShortcut(newShortcut);
}
/**
@@ -410,6 +437,16 @@ class ShortcutPackage extends ShortcutPackageItem {
}
forceReplaceShortcutInner(newShortcut);
+ // TODO: Report usage can be filed async
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.reportUsage(
+ new ReportUsageRequest.Builder(getPackageName())
+ .setUri(newShortcut.getId()).build(),
+ mShortcutUser.mExecutor, result -> future.complete(result.isSuccess()));
+ return future;
+ });
+
return deleted;
}
@@ -419,19 +456,12 @@ class ShortcutPackage extends ShortcutPackageItem {
* @return List of removed shortcuts.
*/
private List<ShortcutInfo> removeOrphans() {
- List<ShortcutInfo> removeList = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.isAlive()) continue;
-
- if (removeList == null) {
- removeList = new ArrayList<>();
- }
+ final List<ShortcutInfo> removeList = new ArrayList<>(1);
+ forEachShortcut(si -> {
+ if (si.isAlive()) return;
removeList.add(si);
- }
- if (removeList != null) {
+ });
+ if (!removeList.isEmpty()) {
for (int i = removeList.size() - 1; i >= 0; i--) {
forceDeleteShortcutInner(removeList.get(i).getId());
}
@@ -448,20 +478,19 @@ class ShortcutPackage extends ShortcutPackageItem {
public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
- boolean changed = false;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final boolean[] changed = new boolean[1];
+ forEachShortcutMutateIf(si -> {
if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
- changed = true;
+ changed[0] = true;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- shortcut.setRank(0); // It may still be pinned, so clear the rank.
- });
+ si.setTimestamp(now);
+ si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ si.setRank(0); // It may still be pinned, so clear the rank.
+ return true;
}
- }
- if (changed) {
+ return false;
+ });
+ if (changed[0]) {
return removeOrphans();
}
return null;
@@ -606,11 +635,6 @@ class ShortcutPackage extends ShortcutPackageItem {
* <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
*/
public void refreshPinnedFlags() {
- final List<ShortcutInfo> shortcuts = new ArrayList<>(mShortcuts.values());
- final Map<String, ShortcutInfo> shortcutMap = new ArrayMap<>(shortcuts.size());
- for (ShortcutInfo si : shortcuts) {
- shortcutMap.put(si.getId(), si);
- }
final Set<String> pinnedShortcuts = new ArraySet<>();
// First, for the pinned set for each launcher, keep track of their id one by one.
@@ -620,31 +644,20 @@ class ShortcutPackage extends ShortcutPackageItem {
if (pinned == null || pinned.size() == 0) {
return;
}
- for (int i = pinned.size() - 1; i >= 0; i--) {
- final String id = pinned.valueAt(i);
- final ShortcutInfo si = shortcutMap.get(id);
- if (si == null) {
- // This happens if a launcher pinned shortcuts from this package, then backup&
- // restored, but this package doesn't allow backing up.
- // In that case the launcher ends up having a dangling pinned shortcuts.
- // That's fine, when the launcher is restored, we'll fix it.
- continue;
- }
- pinnedShortcuts.add(si.getId());
- }
+ pinnedShortcuts.addAll(pinned);
});
// Then, update the pinned state if necessary
- for (int i = shortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = shortcuts.get(i);
+ forEachShortcutMutateIf(si -> {
if (pinnedShortcuts.contains(si.getId()) && !si.isPinned()) {
- mutateShortcut(si.getId(), si,
- shortcut -> shortcut.addFlags(ShortcutInfo.FLAG_PINNED));
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) {
- mutateShortcut(si.getId(), si, shortcut ->
- shortcut.clearFlags(ShortcutInfo.FLAG_PINNED));
+ si.clearFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
- }
+ return false;
+ });
// Lastly, remove the ones that are no longer pinned, cached nor dynamic.
removeOrphans();
@@ -759,9 +772,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
.getPinnedShortcutIds(getPackageName(), getPackageUserId());
-
- for (int i = 0; i < mShortcuts.size(); i++) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
// Need to adjust PINNED flag depending on the caller.
// Basically if the caller is a launcher (callingLauncher != null) and the launcher
// isn't pinning it, then we need to clear PINNED for this caller.
@@ -771,7 +782,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (!getPinnedByAnyLauncher) {
if (si.isFloating() && !si.isCached()) {
if (!isPinnedByCaller) {
- continue;
+ return;
}
}
}
@@ -791,7 +802,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
result.add(clone);
}
- }
+ });
}
public void resetThrottling() {
@@ -861,7 +872,7 @@ class ShortcutPackage extends ShortcutPackageItem {
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) {
+ if (getShortcutCount() == 0 || mShareTargets.isEmpty()) {
return 0;
}
@@ -899,14 +910,12 @@ class ShortcutPackage extends ShortcutPackageItem {
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
public ArraySet<String> getUsedBitmapFiles() {
- final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final ArraySet<String> usedFiles = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.getBitmapPath() != null) {
usedFiles.add(getFileName(si.getBitmapPath()));
}
- }
+ });
return usedFiles;
}
@@ -923,30 +932,29 @@ class ShortcutPackage extends ShortcutPackageItem {
* @return false if any of the target activities are no longer enabled.
*/
private boolean areAllActivitiesStillEnabled() {
- if (mShortcuts.size() == 0) {
- return true;
- }
final ShortcutService s = mShortcutUser.mService;
// Normally the number of target activities is 1 or so, so no need to use a complex
// structure like a set.
final ArrayList<ComponentName> checked = new ArrayList<>(4);
+ final boolean[] reject = new boolean[1];
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutStopWhen(si -> {
final ComponentName activity = si.getActivity();
if (checked.contains(activity)) {
- continue; // Already checked.
+ return false; // Already checked.
}
checked.add(activity);
if ((activity != null)
&& !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
- return false;
+ reject[0] = true;
+ return true; // Found at least 1 activity is disabled, so skip the rest.
}
- }
- return true;
+ return false;
+ });
+ return !reject[0];
}
/**
@@ -1029,32 +1037,32 @@ class ShortcutPackage extends ShortcutPackageItem {
// See if there are any shortcuts that were prevented restoring because the app was of a
// lower version, and re-enable them.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
- continue;
+ return false;
}
if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
si.getId(), getPackageInfo().getBackupSourceVersionCode()));
}
- continue;
+ return false;
}
Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
- shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
- });
- }
+ if (si.hasFlags(ShortcutInfo.FLAG_DISABLED)
+ || si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+ return true;
+ }
+ return false;
+ });
// For existing shortcuts, update timestamps if they have any resources.
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
if (!isNewApp) {
- Resources publisherRes = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final Resources publisherRes = getPackageResources();
+ forEachShortcutMutateIf(si -> {
// Disable dynamic shortcuts whose target activity is gone.
if (si.isDynamic()) {
if (si.getActivity() == null) {
@@ -1067,33 +1075,26 @@ class ShortcutPackage extends ShortcutPackageItem {
getPackageName(), si.getId()));
if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
ShortcutInfo.DISABLED_REASON_APP_CHANGED) != null) {
- continue; // Actually removed.
+ return false; // Actually removed.
}
// Still pinned, so fall-through and possibly update the resources.
}
}
- if (si.hasAnyResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- if (!shortcut.isOriginallyFromManifest()) {
- shortcut.lookupAndFillInResourceIds(res);
- }
+ if (!si.hasAnyResources() || publisherRes == null) {
+ return false;
+ }
- // If this shortcut is not from a manifest, then update all resource IDs
- // from resource names. (We don't allow resource strings for
- // non-manifest at the moment, but icons can still be resources.)
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
+ if (!si.isOriginallyFromManifest()) {
+ si.lookupAndFillInResourceIds(publisherRes);
}
- }
+
+ // If this shortcut is not from a manifest, then update all resource IDs
+ // from resource names. (We don't allow resource strings for
+ // non-manifest at the moment, but icons can still be resources.)
+ si.setTimestamp(s.injectCurrentTimeMillis());
+ return true;
+ });
}
// (Re-)publish manifest shortcut.
@@ -1119,17 +1120,12 @@ class ShortcutPackage extends ShortcutPackageItem {
boolean changed = false;
// Keep the previous IDs.
- ArraySet<String> toDisableList = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ final ArraySet<String> toDisableList = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.isManifestShortcut()) {
- if (toDisableList == null) {
- toDisableList = new ArraySet<>();
- }
toDisableList.add(si.getId());
}
- }
+ });
// Publish new ones.
if (newManifestShortcutList != null) {
@@ -1169,7 +1165,7 @@ class ShortcutPackage extends ShortcutPackageItem {
// regardless.
forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
- if (!newDisabled && toDisableList != null) {
+ if (!newDisabled && !toDisableList.isEmpty()) {
// Still alive, don't remove.
toDisableList.remove(id);
}
@@ -1177,7 +1173,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
// Disable the previous manifest shortcuts that are no longer in the manifest.
- if (toDisableList != null) {
+ if (!toDisableList.isEmpty()) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format(
"Package %s: disabling %d stale shortcuts", getPackageName(),
@@ -1266,25 +1262,21 @@ class ShortcutPackage extends ShortcutPackageItem {
private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
= new ArrayMap<>();
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (si.isFloating()) {
- continue; // Ignore floating shortcuts, which are not tied to any activities.
+ return; // Ignore floating shortcuts, which are not tied to any activities.
}
final ComponentName activity = si.getActivity();
if (activity == null) {
mShortcutUser.mService.wtf("null activity detected.");
- continue;
+ return;
}
- ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
- if (list == null) {
- list = new ArrayList<>();
- activitiesToShortcuts.put(activity, list);
- }
+ ArrayList<ShortcutInfo> list = activitiesToShortcuts.computeIfAbsent(activity,
+ k -> new ArrayList<>());
list.add(si);
- }
+ });
return activitiesToShortcuts;
}
@@ -1320,14 +1312,13 @@ class ShortcutPackage extends ShortcutPackageItem {
// (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
// anyway.)
final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo shortcut = mShortcuts.valueAt(i);
+ forEachShortcut(shortcut -> {
if (shortcut.isManifestShortcut()) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
} else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
}
- }
+ });
for (int i = newList.size() - 1; i >= 0; i--) {
final ShortcutInfo newShortcut = newList.get(i);
@@ -1340,7 +1331,7 @@ class ShortcutPackage extends ShortcutPackageItem {
continue; // Activity can be null for update.
}
- final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo original = findShortcutById(newShortcut.getId());
if (original == null) {
if (operation == ShortcutService.OPERATION_UPDATE) {
continue; // When updating, ignore if there's no target.
@@ -1379,31 +1370,17 @@ class ShortcutPackage extends ShortcutPackageItem {
public void resolveResourceStrings() {
final ShortcutService s = mShortcutUser.mService;
- List<ShortcutInfo> changedShortcuts = null;
+ final Resources publisherRes = getPackageResources();
+ final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
- Resources publisherRes = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.hasStringResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.resolveResourceStrings(res);
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
-
- if (changedShortcuts == null) {
- changedShortcuts = new ArrayList<>(1);
- }
+ if (publisherRes != null) {
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasStringResources()) return false;
+ si.resolveResourceStrings(publisherRes);
+ si.setTimestamp(s.injectCurrentTimeMillis());
changedShortcuts.add(si);
- }
+ return true;
+ });
}
if (!CollectionUtils.isEmpty(changedShortcuts)) {
s.packageShortcutsChanged(getPackageName(), getPackageUserId(), changedShortcuts, null);
@@ -1412,10 +1389,7 @@ class ShortcutPackage extends ShortcutPackageItem {
/** Clears the implicit ranks for all shortcuts. */
public void clearAllImplicitRanks() {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, ShortcutInfo::clearImplicitRankAndRankChangedFlag);
- }
+ forEachShortcutMutate(ShortcutInfo::clearImplicitRankAndRankChangedFlag);
}
/**
@@ -1455,17 +1429,16 @@ class ShortcutPackage extends ShortcutPackageItem {
final long now = s.injectCurrentTimeMillis();
// First, clear ranks for floating shortcuts.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.isFloating()) {
if (si.getRank() != 0) {
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.setRank(0);
- });
+ si.setTimestamp(now);
+ si.setRank(0);
+ return true;
}
}
- }
+ return false;
+ });
// Then adjust ranks. Ranks are unique for each activity, so we first need to sort
// shortcuts to each activity.
@@ -1507,13 +1480,14 @@ class ShortcutPackage extends ShortcutPackageItem {
/** @return true if there's any shortcuts that are not manifest shortcuts. */
public boolean hasNonManifestShortcuts() {
final boolean[] condition = new boolean[1];
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutStopWhen(si -> {
if (!si.isDeclaredInManifest()) {
+ condition[0] = true;
return true;
}
- }
- return false;
+ return false;
+ });
+ return condition[0];
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
@@ -1553,11 +1527,8 @@ class ShortcutPackage extends ShortcutPackageItem {
pw.print(prefix);
pw.println(" Shortcuts:");
- long totalBitmapSize = 0;
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ final long[] totalBitmapSize = new long[1];
+ forEachShortcut(si -> {
pw.println(si.toDumpString(prefix + " "));
if (si.getBitmapPath() != null) {
final long len = new File(si.getBitmapPath()).length();
@@ -1566,15 +1537,15 @@ class ShortcutPackage extends ShortcutPackageItem {
pw.print("bitmap size=");
pw.println(len);
- totalBitmapSize += len;
+ totalBitmapSize[0] += len;
}
- }
+ });
pw.print(prefix);
pw.print(" ");
pw.print("Total bitmap size: ");
- pw.print(totalBitmapSize);
+ pw.print(totalBitmapSize[0]);
pw.print(" (");
- pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
+ pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
pw.println(")");
}
@@ -1589,46 +1560,39 @@ class ShortcutPackage extends ShortcutPackageItem {
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
| (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ forEachShortcut(si -> {
if ((si.getFlags() & shortcutFlags) != 0) {
pw.println(si.toDumpString(""));
}
- }
+ });
}
@Override
public JSONObject dumpCheckin(boolean clear) throws JSONException {
final JSONObject result = super.dumpCheckin(clear);
- int numDynamic = 0;
- int numPinned = 0;
- int numManifest = 0;
- int numBitmaps = 0;
- long totalBitmapSize = 0;
+ final int[] numDynamic = new int[1];
+ final int[] numPinned = new int[1];
+ final int[] numManifest = new int[1];
+ final int[] numBitmaps = new int[1];
+ final long[] totalBitmapSize = new long[1];
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
-
- if (si.isDynamic()) numDynamic++;
- if (si.isDeclaredInManifest()) numManifest++;
- if (si.isPinned()) numPinned++;
+ forEachShortcut(si -> {
+ if (si.isDynamic()) numDynamic[0]++;
+ if (si.isDeclaredInManifest()) numManifest[0]++;
+ if (si.isPinned()) numPinned[0]++;
if (si.getBitmapPath() != null) {
- numBitmaps++;
- totalBitmapSize += new File(si.getBitmapPath()).length();
+ numBitmaps[0]++;
+ totalBitmapSize[0] += new File(si.getBitmapPath()).length();
}
- }
+ });
- result.put(KEY_DYNAMIC, numDynamic);
- result.put(KEY_MANIFEST, numManifest);
- result.put(KEY_PINNED, numPinned);
- result.put(KEY_BITMAPS, numBitmaps);
- result.put(KEY_BITMAP_BYTES, totalBitmapSize);
+ result.put(KEY_DYNAMIC, numDynamic[0]);
+ result.put(KEY_MANIFEST, numManifest[0]);
+ result.put(KEY_PINNED, numPinned[0]);
+ result.put(KEY_BITMAPS, numBitmaps[0]);
+ result.put(KEY_BITMAP_BYTES, totalBitmapSize[0]);
// TODO Log update frequency too.
@@ -1652,9 +1616,15 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
- for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup,
- getPackageInfo().isBackupAllowed());
+ if (forBackup) {
+ // Shortcuts are persisted in AppSearch, xml is only needed for backup.
+ forEachShortcut(si -> {
+ try {
+ saveShortcut(out, si, forBackup, getPackageInfo().isBackupAllowed());
+ } catch (IOException | XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
if (!forBackup) {
@@ -1771,12 +1741,14 @@ class ShortcutPackage extends ShortcutPackageItem {
}
final Intent[] intentsNoExtras = si.getIntentsNoExtras();
final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
- final int numIntents = intentsNoExtras.length;
- for (int i = 0; i < numIntents; i++) {
- out.startTag(null, TAG_INTENT);
- ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
- out.endTag(null, TAG_INTENT);
+ if (intentsNoExtras != null && intentsExtras != null) {
+ final int numIntents = intentsNoExtras.length;
+ for (int i = 0; i < numIntents; i++) {
+ out.startTag(null, TAG_INTENT);
+ ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+ out.endTag(null, TAG_INTENT);
+ }
}
ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
@@ -1864,7 +1836,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final ShortcutInfo si = parseShortcut(parser, packageName,
shortcutUser.getUserId(), fromBackup);
// Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
+ ret.mShortcuts.add(si);
continue;
case TAG_SHARE_TARGET:
ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
@@ -2060,7 +2032,9 @@ class ShortcutPackage extends ShortcutPackageItem {
@VisibleForTesting
List<ShortcutInfo> getAllShortcutsForTest() {
- return new ArrayList<>(mShortcuts.values());
+ final List<ShortcutInfo> ret = new ArrayList<>(1);
+ forEachShortcut(ret::add);
+ return ret;
}
@VisibleForTesting
@@ -2072,7 +2046,7 @@ class ShortcutPackage extends ShortcutPackageItem {
public void verifyStates() {
super.verifyStates();
- boolean failed = false;
+ final boolean[] failed = new boolean[1];
final ShortcutService s = mShortcutUser.mService;
@@ -2083,7 +2057,7 @@ class ShortcutPackage extends ShortcutPackageItem {
for (int outer = all.size() - 1; outer >= 0; outer--) {
final ArrayList<ShortcutInfo> list = all.valueAt(outer);
if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
+ " has " + all.valueAt(outer).size() + " shortcuts.");
}
@@ -2103,61 +2077,60 @@ class ShortcutPackage extends ShortcutPackageItem {
}
// Verify each shortcut's status.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned() || si.isCached())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not manifest, dynamic or pinned.");
}
if (si.isDeclaredInManifest() && si.isDynamic()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is both dynamic and manifest at the same time.");
}
if (si.getActivity() == null && !si.isFloating()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has null activity, but not floating.");
}
if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not floating, but is disabled.");
}
if (si.isFloating() && si.getRank() != 0) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is floating, but has rank=" + si.getRank());
}
if (si.getIcon() != null) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " still has an icon");
}
if (si.hasAdaptiveBitmap() && !(si.hasIconFile() || si.hasIconUri())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has adaptive bitmap but was not saved to a file nor has icon uri.");
}
if (si.hasIconFile() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both resource and bitmap icons");
}
if (si.hasIconFile() && si.hasIconUri()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and bitmap icons");
}
if (si.hasIconUri() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and resource icons");
}
if (si.isEnabled()
!= (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " isEnabled() and getDisabledReason() disagree: "
+ si.isEnabled() + " vs " + si.getDisabledReason());
@@ -2165,18 +2138,18 @@ class ShortcutPackage extends ShortcutPackageItem {
if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
&& (getPackageInfo().getBackupSourceVersionCode()
== ShortcutInfo.VERSION_CODE_UNKNOWN)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " RESTORED_VERSION_LOWER with no backup source version code.");
}
if (s.isDummyMainActivity(si.getActivity())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has a dummy target activity");
}
- }
+ });
- if (failed) {
+ if (failed[0]) {
throw new IllegalStateException("See logcat for errors");
}
}
@@ -2187,6 +2160,7 @@ class ShortcutPackage extends ShortcutPackageItem {
} else {
mPackageIdentifiers.remove(packageName);
}
+ resetAppSearch(session -> AndroidFuture.completedFuture(true));
}
void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
@@ -2196,23 +2170,302 @@ class ShortcutPackage extends ShortcutPackageItem {
synchronized (mLock) {
if (shortcut != null) {
transform.accept(shortcut);
- } else {
- transform.accept(findShortcutById(id));
}
- // TODO: Load ShortcutInfo from AppSearch, apply transformation logic and save
+ final ShortcutInfo si = getShortcutById(id);
+ if (si == null) {
+ return;
+ }
+ transform.accept(si);
+ saveShortcut(si);
}
}
+ private void saveShortcut(@NonNull final ShortcutInfo... shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ saveShortcut(Arrays.asList(shortcuts));
+ }
+
+ private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ if (shortcuts.isEmpty()) {
+ // No need to invoke AppSearch when there's nothing to save.
+ return;
+ }
+ ConcurrentUtils.waitForFutureNoInterrupt(
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.put(new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ AppSearchShortcutInfo.toGenericDocuments(shortcuts))
+ .build(),
+ mShortcutUser.mExecutor,
+ result -> {
+ if (!result.isSuccess()) {
+ for (AppSearchResult<Void> k : result.getFailures().values()) {
+ Slog.e(TAG, k.getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "failed to save shortcuts"));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ }),
+ "saving shortcut");
+ }
+
/**
* Removes shortcuts from AppSearch.
*/
void removeShortcuts() {
+ awaitInAppSearch("removing shortcuts", session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove("",
+ new SearchSpec.Builder()
+ .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(new RuntimeException(
+ "Failed to cleanup shortcuts " + result.getErrorMessage()));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private void removeShortcut(@NonNull final String id) {
+ Objects.requireNonNull(id);
+ awaitInAppSearch("removing shortcut with id=" + id, session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove(new RemoveByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ final Map<String, AppSearchResult<Void>> failures =
+ result.getFailures();
+ for (String key : failures.keySet()) {
+ Slog.e(TAG, "Failed deleting " + key + ", error message:"
+ + failures.get(key).getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "Failed to delete shortcut: " + id));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private ShortcutInfo getShortcutById(String id) {
+ return awaitInAppSearch("getting shortcut with id=" + id, session -> {
+ final AndroidFuture<ShortcutInfo> future = new AndroidFuture<>();
+ session.getByUri(
+ new GetByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor,
+ results -> {
+ if (results.isSuccess()) {
+ Map<String, GenericDocument> documents = results.getSuccesses();
+ for (GenericDocument doc : documents.values()) {
+ final ShortcutInfo info = new AppSearchShortcutInfo(doc)
+ .toShortcutInfo(mShortcutUser.getUserId());
+ future.complete(info);
+ return;
+ }
+ }
+ future.complete(null);
+ });
+ return future;
+ });
+ }
+
+ private void forEachShortcut(
+ @NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutStopWhen(si -> {
+ cb.accept(si);
+ return false;
+ });
+ }
+
+ private void forEachShortcutMutate(@NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutMutateIf(si -> {
+ cb.accept(si);
+ return true;
+ });
+ }
+
+ private void forEachShortcutMutateIf(@NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("mutating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ final List<ShortcutInfo> changed = new ArrayList<>(1);
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) changed.add(si);
+ }
+ saveShortcut(changed);
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ forEachShortcutStopWhen("", cb);
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("iterating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search(query, new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) return;
+ }
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private List<ShortcutInfo> getNextPage(@NonNull final SearchResults res) {
+ final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
+ final List<ShortcutInfo> ret = new ArrayList<>();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ res.getNextPage(mShortcutUser.mExecutor, nextPage -> {
+ if (!nextPage.isSuccess()) {
+ future.complete(ret);
+ return;
+ }
+ final List<SearchResult> results = nextPage.getResultValue();
+ if (results.isEmpty()) {
+ future.complete(ret);
+ return;
+ }
+ final List<ShortcutInfo> page = new ArrayList<>(results.size());
+ for (SearchResult result : results) {
+ final ShortcutInfo si = new AppSearchShortcutInfo(result.getGenericDocument())
+ .toShortcutInfo(mShortcutUser.getUserId());
+ page.add(si);
+ }
+ ret.addAll(page);
+ future.complete(ret);
+ });
+ return ConcurrentUtils.waitForFutureNoInterrupt(future,
+ "getting next batch of shortcuts");
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Nullable
+ private <T> T awaitInAppSearch(
+ @NonNull final String description,
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ return ConcurrentUtils.waitForFutureNoInterrupt(runInAppSearch(cb), description);
+ }
+
+ @Nullable
+ private <T> CompletableFuture<T> runInAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ synchronized (mLock) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
+ .build());
+ if (mAppSearchSession != null) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return AndroidFuture.supply(() -> mAppSearchSession).thenCompose(cb);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ } else {
+ return resetAppSearch(cb);
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+ }
+
+ private <T> CompletableFuture<T> resetAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ final AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder(getPackageName()).build();
+ final AppSearchSession session;
+ try {
+ session = ConcurrentUtils.waitForFutureNoInterrupt(
+ mShortcutUser.getAppSearch(searchContext), "resetting app search");
+ ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "setting up schema");
+ mAppSearchSession = session;
+ return cb.apply(mAppSearchSession);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initiate app search for shortcut package "
+ + getPackageName() + " user " + mShortcutUser.getUserId(), e);
+ return AndroidFuture.completedFuture(null);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ void closeAppSearchSession() {
+ synchronized (mLock) {
+ if (mAppSearchSession != null) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ mAppSearchSession.close();
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+ mAppSearchSession = null;
+ }
+ }
+
+ @NonNull
+ private AndroidFuture<AppSearchSession> setupSchema(
+ @NonNull final AppSearchSession session) {
+ SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
+ for (PackageIdentifier pi : mPackageIdentifiers.values()) {
+ schemaBuilder = schemaBuilder
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchPerson.SCHEMA_TYPE, true, pi)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
+ }
+ final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
+ session.setSchema(
+ schemaBuilder.build(), mShortcutUser.mExecutor, mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new IllegalArgumentException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(session);
+ });
+ return future;
}
/**
* Merge/replace shortcuts parsed from xml file.
*/
void restoreParsedShortcuts(final boolean replace) {
+ if (replace) {
+ removeShortcuts();
+ }
+ saveShortcut(mShortcuts);
}
private boolean verifyRanksSequential(List<ShortcutInfo> list) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 8e999de80707..4cfa353b36f8 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1051,6 +1051,8 @@ public class ShortcutService extends IShortcutService.Stub {
}
final ShortcutUser user = getUserShortcutsLocked(userId);
+ // Close AppSearchSession to flush pending changes.
+ user.forAllPackages(ShortcutPackage::closeAppSearchSession);
user.logSharingShortcutStats(mMetricsLogger);
}
@@ -5081,6 +5083,17 @@ public class ShortcutService extends IShortcutService.Stub {
}
@VisibleForTesting
+ void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ synchronized (mLock) {
+ final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
+ if (pkg == null) return;
+
+ pkg.mutateShortcut(shortcutId, null, cb);
+ }
+ }
+
+ @VisibleForTesting
ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
synchronized (mLock) {
final ShortcutUser user = mUsers.get(userId);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 589b3b403e6c..88b06511cc74 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -2041,6 +2041,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
}
+ protected void updatePackageShortcut(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ mService.updatePackageShortcutForTest(packageName, shortcutId, userId, cb);
+ }
+
protected void assertShortcutExists(String packageName, String shortcutId, int userId) {
assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
}
@@ -2236,6 +2241,10 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
}
+ protected void updateCallerShortcut(String shortcutId, Consumer<ShortcutInfo> cb) {
+ updatePackageShortcut(getCallingPackage(), shortcutId, getCallingUserId(), cb);
+ }
+
protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
final List<ShortcutInfo>[] ret = new List[1];
runWithCaller(launcher, userId, () -> {
@@ -2395,6 +2404,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
deleteAllSavedFiles();
+ mMockAppSearchManager.removeShortcuts();
+
initService();
mService.applyRestore(payload, USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 4d0beef99cca..c16e49899dc2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1702,8 +1702,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Because setDynamicShortcuts will update the timestamps when ranks are changing,
// we explicitly set timestamps here.
- getCallerShortcut("s1").setTimestamp(5000);
- getCallerShortcut("s2").setTimestamp(1000);
+ updateCallerShortcut("s1", si -> si.setTimestamp(5000));
+ updateCallerShortcut("s2", si -> si.setTimestamp(1000));
setCaller(CALLING_PACKAGE_2);
final ShortcutInfo s2_2 = makeShortcut("s2");
@@ -1713,9 +1713,9 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
makeComponent(ShortcutActivity.class));
assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
- getCallerShortcut("s2").setTimestamp(1500);
- getCallerShortcut("s3").setTimestamp(3000);
- getCallerShortcut("s4").setTimestamp(500);
+ updateCallerShortcut("s2", si -> si.setTimestamp(1500));
+ updateCallerShortcut("s3", si -> si.setTimestamp(3000));
+ updateCallerShortcut("s4", si -> si.setTimestamp(500));
setCaller(CALLING_PACKAGE_3);
final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
@@ -1723,7 +1723,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
- getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
+ updateCallerShortcut("s3", si -> si.setTimestamp(START_TIME + 5000));
setCaller(LAUNCHER_1);
@@ -7686,7 +7686,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals("http://www/", si.getIntent().getData().toString());
assertEquals("foo/bar", si.getIntent().getType());
assertEquals(
- new ComponentName("abc", ".xyz"), si.getIntent().getComponent());
+ new ComponentName("abc", "abc.xyz"), si.getIntent().getComponent());
assertEquals(set("cat1", "cat2"), si.getIntent().getCategories());
assertEquals("value1", si.getIntent().getStringExtra("key1"));