summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/AppSearchShortcutInfo.java262
-rw-r--r--core/java/android/content/pm/ShortcutInfo.java8
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java946
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java170
-rw-r--r--services/core/java/com/android/server/pm/ShortcutUser.java4
-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
7 files changed, 1038 insertions, 377 deletions
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index b2478ca12191..6d5829c175b5 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -52,6 +52,7 @@ public class AppSearchShortcutInfo extends GenericDocument {
/** The name of the schema type for {@link ShortcutInfo} documents.*/
public static final String SCHEMA_TYPE = "Shortcut";
+ public static final int SCHEMA_VERSION = 1;
public static final String KEY_ACTIVITY = "activity";
public static final String KEY_SHORT_LABEL = "shortLabel";
@@ -163,8 +164,10 @@ public class AppSearchShortcutInfo extends GenericDocument {
.setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
.build()
- ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_RANK)
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_RANK)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
.build()
).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_IMPLICIT_RANK)
@@ -175,8 +178,10 @@ public class AppSearchShortcutInfo extends GenericDocument {
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.build()
- ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_FLAGS)
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_FLAGS)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
.build()
).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_ICON_RES_ID)
@@ -201,12 +206,111 @@ public class AppSearchShortcutInfo extends GenericDocument {
.setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
.build()
- ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_DISABLED_REASON)
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_DISABLED_REASON)
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
.build()
).build();
+ /**
+ * The string representation of every flag within {@link ShortcutInfo}. Note that its value
+ * needs to be camelCase since AppSearch's tokenizer will break the word when it sees
+ * underscore.
+ */
+ private static final String IS_DYNAMIC = "Dyn";
+ private static final String NOT_DYNAMIC = "nDyn";
+ private static final String IS_PINNED = "Pin";
+ private static final String NOT_PINNED = "nPin";
+ private static final String HAS_ICON_RES = "IcR";
+ private static final String NO_ICON_RES = "nIcR";
+ private static final String HAS_ICON_FILE = "IcF";
+ private static final String NO_ICON_FILE = "nIcF";
+ private static final String IS_KEY_FIELD_ONLY = "Key";
+ private static final String NOT_KEY_FIELD_ONLY = "nKey";
+ private static final String IS_MANIFEST = "Man";
+ private static final String NOT_MANIFEST = "nMan";
+ private static final String IS_DISABLED = "Dis";
+ private static final String NOT_DISABLED = "nDis";
+ private static final String ARE_STRINGS_RESOLVED = "Str";
+ private static final String NOT_STRINGS_RESOLVED = "nStr";
+ private static final String IS_IMMUTABLE = "Im";
+ private static final String NOT_IMMUTABLE = "nIm";
+ private static final String HAS_ADAPTIVE_BITMAP = "IcA";
+ private static final String NO_ADAPTIVE_BITMAP = "nIcA";
+ private static final String IS_RETURNED_BY_SERVICE = "Rets";
+ private static final String NOT_RETURNED_BY_SERVICE = "nRets";
+ private static final String HAS_ICON_FILE_PENDING_SAVE = "Pens";
+ private static final String NO_ICON_FILE_PENDING_SAVE = "nPens";
+ private static final String IS_SHADOW = "Sdw";
+ private static final String NOT_SHADOW = "nSdw";
+ private static final String IS_LONG_LIVED = "Liv";
+ private static final String NOT_LONG_LIVED = "nLiv";
+ private static final String HAS_ICON_URI = "IcU";
+ private static final String NO_ICON_URI = "nIcU";
+ private static final String IS_CACHED_NOTIFICATION = "CaN";
+ private static final String NOT_CACHED_NOTIFICATION = "nCaN";
+ private static final String IS_CACHED_BUBBLE = "CaB";
+ private static final String NOT_CACHED_BUBBLE = "nCaB";
+ private static final String IS_CACHED_PEOPLE_TITLE = "CaPT";
+ private static final String NOT_CACHED_PEOPLE_TITLE = "nCaPT";
+
+ /**
+ * Following flags are not store within ShortcutInfo, but book-keeping states to reduce search
+ * space when performing queries against AppSearch.
+ */
+ private static final String HAS_BITMAP_PATH = "hBiP";
+ private static final String HAS_STRING_RESOURCE = "hStr";
+ private static final String HAS_NON_ZERO_RANK = "hRan";
+
+ public static final String QUERY_IS_DYNAMIC = KEY_FLAGS + ":" + IS_DYNAMIC;
+ public static final String QUERY_IS_NOT_DYNAMIC = KEY_FLAGS + ":" + NOT_DYNAMIC;
+ public static final String QUERY_IS_PINNED = KEY_FLAGS + ":" + IS_PINNED;
+ public static final String QUERY_IS_NOT_PINNED = KEY_FLAGS + ":" + NOT_PINNED;
+ public static final String QUERY_IS_MANIFEST = KEY_FLAGS + ":" + IS_MANIFEST;
+ public static final String QUERY_IS_NOT_MANIFEST = KEY_FLAGS + ":" + NOT_MANIFEST;
+ public static final String QUERY_IS_PINNED_AND_ENABLED =
+ "(" + KEY_FLAGS + ":" + IS_PINNED + " " + KEY_FLAGS + ":" + NOT_DISABLED + ")";
+ public static final String QUERY_IS_CACHED =
+ "(" + KEY_FLAGS + ":" + IS_CACHED_NOTIFICATION + " OR "
+ + KEY_FLAGS + ":" + IS_CACHED_BUBBLE + " OR "
+ + KEY_FLAGS + ":" + IS_CACHED_PEOPLE_TITLE + ")";
+ public static final String QUERY_IS_NOT_CACHED =
+ "(" + KEY_FLAGS + ":" + NOT_CACHED_NOTIFICATION + " "
+ + KEY_FLAGS + ":" + NOT_CACHED_BUBBLE + " "
+ + KEY_FLAGS + ":" + NOT_CACHED_PEOPLE_TITLE + ")";
+ public static final String QUERY_IS_FLOATING =
+ "((" + IS_PINNED + " OR " + QUERY_IS_CACHED + ") "
+ + QUERY_IS_NOT_DYNAMIC + " " + QUERY_IS_NOT_MANIFEST + ")";
+ public static final String QUERY_IS_NOT_FLOATING =
+ "((" + QUERY_IS_NOT_PINNED + " " + QUERY_IS_NOT_CACHED + ") OR "
+ + QUERY_IS_DYNAMIC + " OR " + QUERY_IS_MANIFEST + ")";
+ public static final String QUERY_IS_VISIBLE_TO_PUBLISHER =
+ "(" + KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_NOT_DISABLED
+ + " OR " + KEY_DISABLED_REASON + ":"
+ + ShortcutInfo.DISABLED_REASON_BY_APP
+ + " OR " + KEY_DISABLED_REASON + ":"
+ + ShortcutInfo.DISABLED_REASON_APP_CHANGED
+ + " OR " + KEY_DISABLED_REASON + ":"
+ + ShortcutInfo.DISABLED_REASON_UNKNOWN + ")";
+ public static final String QUERY_DISABLED_REASON_VERSION_LOWER =
+ KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
+ public static final String QUERY_IS_NON_MANIFEST_VISIBLE =
+ "(" + QUERY_IS_NOT_MANIFEST + " " + QUERY_IS_VISIBLE_TO_PUBLISHER + " ("
+ + QUERY_IS_PINNED + " OR " + QUERY_IS_CACHED + " OR " + QUERY_IS_DYNAMIC + "))";
+ public static final String QUERY_IS_VISIBLE_CACHED_OR_PINNED =
+ "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_DYNAMIC
+ + " (" + QUERY_IS_CACHED + " OR " + QUERY_IS_PINNED + "))";
+ public static final String QUERY_IS_VISIBLE_PINNED_ONLY =
+ "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_PINNED + " " + QUERY_IS_NOT_CACHED
+ + " " + QUERY_IS_NOT_DYNAMIC + " " + QUERY_IS_NOT_MANIFEST + ")";
+ public static final String QUERY_HAS_BITMAP_PATH = KEY_FLAGS + ":" + HAS_BITMAP_PATH;
+ public static final String QUERY_HAS_STRING_RESOURCE = KEY_FLAGS + ":" + HAS_STRING_RESOURCE;
+ public static final String QUERY_HAS_NON_ZERO_RANK = KEY_FLAGS + ":" + HAS_NON_ZERO_RANK;
+ public static final String QUERY_IS_FLOATING_AND_HAS_RANK =
+ "(" + QUERY_IS_FLOATING + " " + QUERY_HAS_NON_ZERO_RANK + ")";
+
public AppSearchShortcutInfo(@NonNull GenericDocument document) {
super(document);
}
@@ -304,16 +408,16 @@ public class AppSearchShortcutInfo extends GenericDocument {
final Person[] persons = parsePerson(getPropertyDocumentArray(KEY_PERSON));
final String locusIdString = getPropertyString(KEY_LOCUS_ID);
final LocusId locusId = locusIdString == null ? null : new LocusId(locusIdString);
- final int rank = (int) getPropertyLong(KEY_RANK);
+ final int rank = Integer.parseInt(getPropertyString(KEY_RANK));
final int implicitRank = (int) getPropertyLong(KEY_IMPLICIT_RANK);
final byte[] extrasByte = getPropertyBytes(KEY_EXTRAS);
final PersistableBundle extras = transformToPersistableBundle(extrasByte);
- final int flags = parseFlags(getPropertyLongArray(KEY_FLAGS));
+ final int flags = parseFlags(getPropertyStringArray(KEY_FLAGS));
final int iconResId = (int) getPropertyLong(KEY_ICON_RES_ID);
final String iconResName = getPropertyString(KEY_ICON_RES_NAME);
final String iconUri = getPropertyString(KEY_ICON_URI);
final String bitmapPath = getPropertyString(KEY_BITMAP_PATH);
- final int disabledReason = (int) getPropertyLong(KEY_DISABLED_REASON);
+ final int disabledReason = Integer.parseInt(getPropertyString(KEY_DISABLED_REASON));
final ShortcutInfo si = new ShortcutInfo(
userId, getUri(), packageName, activity, icon, shortLabel, shortLabelResId,
shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage,
@@ -344,6 +448,9 @@ public class AppSearchShortcutInfo extends GenericDocument {
@VisibleForTesting
public static class Builder extends GenericDocument.Builder<Builder> {
+ private List<String> mFlags = new ArrayList<>(1);
+ private boolean mHasStringResource = false;
+
public Builder(String packageName, String id) {
super(/*namespace=*/ packageName, id, SCHEMA_TYPE);
}
@@ -386,8 +493,11 @@ public class AppSearchShortcutInfo extends GenericDocument {
* @hide
*/
@NonNull
- public Builder setShortLabelResId(@Nullable final int shortLabelResId) {
+ public Builder setShortLabelResId(final int shortLabelResId) {
setPropertyLong(KEY_SHORT_LABEL_RES_ID, shortLabelResId);
+ if (shortLabelResId != 0) {
+ mHasStringResource = true;
+ }
return this;
}
@@ -417,8 +527,11 @@ public class AppSearchShortcutInfo extends GenericDocument {
* @hide
*/
@NonNull
- public Builder setLongLabelResId(@Nullable final int longLabelResId) {
+ public Builder setLongLabelResId(final int longLabelResId) {
setPropertyLong(KEY_LONG_LABEL_RES_ID, longLabelResId);
+ if (longLabelResId != 0) {
+ mHasStringResource = true;
+ }
return this;
}
@@ -448,8 +561,11 @@ public class AppSearchShortcutInfo extends GenericDocument {
* @hide
*/
@NonNull
- public Builder setDisabledMessageResId(@Nullable final int disabledMessageResId) {
+ public Builder setDisabledMessageResId(final int disabledMessageResId) {
setPropertyLong(KEY_DISABLED_MESSAGE_RES_ID, disabledMessageResId);
+ if (disabledMessageResId != 0) {
+ mHasStringResource = true;
+ }
return this;
}
@@ -546,7 +662,10 @@ public class AppSearchShortcutInfo extends GenericDocument {
@NonNull
public Builder setRank(final int rank) {
Preconditions.checkArgument((0 <= rank), "Rank cannot be negative");
- setPropertyLong(KEY_RANK, rank);
+ setPropertyString(KEY_RANK, String.valueOf(rank));
+ if (rank != 0) {
+ mFlags.add(HAS_NON_ZERO_RANK);
+ }
return this;
}
@@ -574,7 +693,10 @@ public class AppSearchShortcutInfo extends GenericDocument {
* @hide
*/
public Builder setFlags(@ShortcutInfo.ShortcutFlags final int flags) {
- setPropertyLong(KEY_FLAGS, flattenFlags(flags));
+ final String[] flagArray = flattenFlags(flags);
+ if (flagArray != null && flagArray.length > 0) {
+ mFlags.addAll(Arrays.asList(flagArray));
+ }
return this;
}
@@ -603,6 +725,7 @@ public class AppSearchShortcutInfo extends GenericDocument {
public Builder setBitmapPath(@Nullable final String bitmapPath) {
if (!TextUtils.isEmpty(bitmapPath)) {
setPropertyString(KEY_BITMAP_PATH, bitmapPath);
+ mFlags.add(HAS_BITMAP_PATH);
}
return this;
}
@@ -621,7 +744,7 @@ public class AppSearchShortcutInfo extends GenericDocument {
* @hide
*/
public Builder setDisabledReason(@ShortcutInfo.DisabledReason final int disabledReason) {
- setPropertyLong(KEY_DISABLED_REASON, disabledReason);
+ setPropertyString(KEY_DISABLED_REASON, String.valueOf(disabledReason));
return this;
}
@@ -631,6 +754,10 @@ public class AppSearchShortcutInfo extends GenericDocument {
@NonNull
@Override
public AppSearchShortcutInfo build() {
+ if (mHasStringResource) {
+ mFlags.add(HAS_STRING_RESOURCE);
+ }
+ setPropertyString(KEY_FLAGS, mFlags.toArray(new String[0]));
return new AppSearchShortcutInfo(super.build());
}
}
@@ -682,20 +809,115 @@ public class AppSearchShortcutInfo extends GenericDocument {
}
}
- private static long[] flattenFlags(@ShortcutInfo.ShortcutFlags final int flags) {
- final List<Integer> flattenedFlags = new ArrayList<>();
- flattenedFlags.add(0);
+ private static String[] flattenFlags(@ShortcutInfo.ShortcutFlags final int flags) {
+ final List<String> flattenedFlags = new ArrayList<>();
for (int i = 0; i < 31; i++) {
final int mask = 1 << i;
- if ((flags & mask) != 0) {
- flattenedFlags.add(mask);
+ final String value = flagToString(flags, mask);
+ if (value != null) {
+ flattenedFlags.add(value);
}
}
- return flattenedFlags.stream().mapToLong(i -> i).toArray();
+ return flattenedFlags.toArray(new String[0]);
+ }
+
+ @Nullable
+ private static String flagToString(
+ @ShortcutInfo.ShortcutFlags final int flags, final int mask) {
+ switch (mask) {
+ case ShortcutInfo.FLAG_DYNAMIC:
+ return (flags & mask) != 0 ? IS_DYNAMIC : NOT_DYNAMIC;
+ case ShortcutInfo.FLAG_PINNED:
+ return (flags & mask) != 0 ? IS_PINNED : NOT_PINNED;
+ case ShortcutInfo.FLAG_HAS_ICON_RES:
+ return (flags & mask) != 0 ? HAS_ICON_RES : NO_ICON_RES;
+ case ShortcutInfo.FLAG_HAS_ICON_FILE:
+ return (flags & mask) != 0 ? HAS_ICON_FILE : NO_ICON_FILE;
+ case ShortcutInfo.FLAG_KEY_FIELDS_ONLY:
+ return (flags & mask) != 0 ? IS_KEY_FIELD_ONLY : NOT_KEY_FIELD_ONLY;
+ case ShortcutInfo.FLAG_MANIFEST:
+ return (flags & mask) != 0 ? IS_MANIFEST : NOT_MANIFEST;
+ case ShortcutInfo.FLAG_DISABLED:
+ return (flags & mask) != 0 ? IS_DISABLED : NOT_DISABLED;
+ case ShortcutInfo.FLAG_STRINGS_RESOLVED:
+ return (flags & mask) != 0 ? ARE_STRINGS_RESOLVED : NOT_STRINGS_RESOLVED;
+ case ShortcutInfo.FLAG_IMMUTABLE:
+ return (flags & mask) != 0 ? IS_IMMUTABLE : NOT_IMMUTABLE;
+ case ShortcutInfo.FLAG_ADAPTIVE_BITMAP:
+ return (flags & mask) != 0 ? HAS_ADAPTIVE_BITMAP : NO_ADAPTIVE_BITMAP;
+ case ShortcutInfo.FLAG_RETURNED_BY_SERVICE:
+ return (flags & mask) != 0 ? IS_RETURNED_BY_SERVICE : NOT_RETURNED_BY_SERVICE;
+ case ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE:
+ return (flags & mask) != 0 ? HAS_ICON_FILE_PENDING_SAVE : NO_ICON_FILE_PENDING_SAVE;
+ case ShortcutInfo.FLAG_SHADOW:
+ return (flags & mask) != 0 ? IS_SHADOW : NOT_SHADOW;
+ case ShortcutInfo.FLAG_LONG_LIVED:
+ return (flags & mask) != 0 ? IS_LONG_LIVED : NOT_LONG_LIVED;
+ case ShortcutInfo.FLAG_HAS_ICON_URI:
+ return (flags & mask) != 0 ? HAS_ICON_URI : NO_ICON_URI;
+ case ShortcutInfo.FLAG_CACHED_NOTIFICATIONS:
+ return (flags & mask) != 0 ? IS_CACHED_NOTIFICATION : NOT_CACHED_NOTIFICATION;
+ case ShortcutInfo.FLAG_CACHED_BUBBLES:
+ return (flags & mask) != 0 ? IS_CACHED_BUBBLE : NOT_CACHED_BUBBLE;
+ case ShortcutInfo.FLAG_CACHED_PEOPLE_TILE:
+ return (flags & mask) != 0 ? IS_CACHED_PEOPLE_TITLE : NOT_CACHED_PEOPLE_TITLE;
+ default:
+ return null;
+ }
}
- private static int parseFlags(final long[] flags) {
- return (int) Arrays.stream(flags).reduce((p, v) -> p | v).getAsLong();
+ private static int parseFlags(@Nullable final String[] flags) {
+ if (flags == null) {
+ return 0;
+ }
+ int ret = 0;
+ for (int i = 0; i < flags.length; i++) {
+ ret = ret | parseFlag(flags[i]);
+ }
+ return ret;
+ }
+
+ private static int parseFlag(final String value) {
+ switch (value) {
+ case IS_DYNAMIC:
+ return ShortcutInfo.FLAG_DYNAMIC;
+ case IS_PINNED:
+ return ShortcutInfo.FLAG_PINNED;
+ case HAS_ICON_RES:
+ return ShortcutInfo.FLAG_HAS_ICON_RES;
+ case HAS_ICON_FILE:
+ return ShortcutInfo.FLAG_HAS_ICON_FILE;
+ case IS_KEY_FIELD_ONLY:
+ return ShortcutInfo.FLAG_KEY_FIELDS_ONLY;
+ case IS_MANIFEST:
+ return ShortcutInfo.FLAG_MANIFEST;
+ case IS_DISABLED:
+ return ShortcutInfo.FLAG_DISABLED;
+ case ARE_STRINGS_RESOLVED:
+ return ShortcutInfo.FLAG_STRINGS_RESOLVED;
+ case IS_IMMUTABLE:
+ return ShortcutInfo.FLAG_IMMUTABLE;
+ case HAS_ADAPTIVE_BITMAP:
+ return ShortcutInfo.FLAG_ADAPTIVE_BITMAP;
+ case IS_RETURNED_BY_SERVICE:
+ return ShortcutInfo.FLAG_RETURNED_BY_SERVICE;
+ case HAS_ICON_FILE_PENDING_SAVE:
+ return ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE;
+ case IS_SHADOW:
+ return ShortcutInfo.FLAG_SHADOW;
+ case IS_LONG_LIVED:
+ return ShortcutInfo.FLAG_LONG_LIVED;
+ case HAS_ICON_URI:
+ return ShortcutInfo.FLAG_HAS_ICON_URI;
+ case IS_CACHED_NOTIFICATION:
+ return ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
+ case IS_CACHED_BUBBLE:
+ return ShortcutInfo.FLAG_CACHED_BUBBLES;
+ case IS_CACHED_PEOPLE_TITLE:
+ return ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
+ default:
+ return 0;
+ }
}
@NonNull
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 275e81c0e868..76712b5ce2dc 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -148,7 +148,13 @@ public final class ShortcutInfo implements Parcelable {
public static final int FLAG_CACHED_ALL =
FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES | FLAG_CACHED_PEOPLE_TILE;
- /** @hide */
+ /**
+ * Bitmask-based flags indicating different states associated with the shortcut. Note that if
+ * new value is added here, consider adding also the corresponding string representation and
+ * queries in {@link AppSearchShortcutInfo}.
+ *
+ * @hide
+ */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_DYNAMIC,
FLAG_PINNED,
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 19c56f82d7bf..e222df04022e 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;
@@ -41,10 +56,11 @@ import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
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 +80,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;
/**
@@ -95,6 +115,7 @@ class ShortcutPackage extends ShortcutPackageItem {
private static final String ATTR_NAME = "name";
private static final String ATTR_CALL_COUNT = "call-count";
private static final String ATTR_LAST_RESET = "last-reset";
+ private static final String ATTR_SCHEMA_VERSON = "schema-version";
private static final String ATTR_ID = "id";
private static final String ATTR_ACTIVITY = "activity";
private static final String ATTR_TITLE = "title";
@@ -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 ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
* All the share targets from the package
@@ -167,8 +188,7 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
private final Map<String, PackageIdentifier> mPackageIdentifiers = new ArrayMap<>(0);
- @GuardedBy("mLock")
- private AppSearchSession mAppSearchSession;
+ private boolean mIsInitilized;
private ShortcutPackage(ShortcutUser shortcutUser,
int packageUserId, String packageName, ShortcutPackageInfo spi) {
@@ -199,7 +219,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 +235,23 @@ 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);
- }
- });
- }
+ final String query = String.format("%s:-%s AND %s:%s",
+ AppSearchShortcutInfo.KEY_FLAGS, ShortcutInfo.FLAG_SHADOW,
+ AppSearchShortcutInfo.KEY_DISABLED_REASON, restoreBlockReason);
+ forEachShortcutMutateIf(query, si -> {
+ if (restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED
+ && !si.hasFlags(ShortcutInfo.FLAG_SHADOW)
+ && si.getDisabledReason() == restoreBlockReason) {
+ 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();
@@ -233,8 +261,10 @@ class ShortcutPackage extends ShortcutPackageItem {
* Note this does *not* provide a correct view to the calling launcher.
*/
@Nullable
- public ShortcutInfo findShortcutById(String id) {
- return mShortcuts.get(id);
+ public ShortcutInfo findShortcutById(@Nullable final String id) {
+ if (id == null) return null;
+ final List<ShortcutInfo> ret = getShortcutById(Collections.singleton(id));
+ return ret.isEmpty() ? null : ret.get(0);
}
public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -300,8 +330,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 = findShortcutById(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);
@@ -321,7 +352,7 @@ class ShortcutPackage extends ShortcutPackageItem {
// 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 +441,15 @@ class ShortcutPackage extends ShortcutPackageItem {
}
forceReplaceShortcutInner(newShortcut);
+ mShortcutUser.mService.injectPostToHandler(() -> awaitInAppSearch("reportUsage",
+ 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 +459,17 @@ 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);
+ final String query = String.format("%s OR %s OR %s OR %s",
+ AppSearchShortcutInfo.QUERY_IS_PINNED,
+ AppSearchShortcutInfo.QUERY_IS_DYNAMIC,
+ AppSearchShortcutInfo.QUERY_IS_MANIFEST,
+ AppSearchShortcutInfo.QUERY_IS_CACHED);
+ forEachShortcut(query, 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());
}
@@ -447,21 +485,27 @@ 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 String query;
+ if (!ignoreInvisible) {
+ query = AppSearchShortcutInfo.QUERY_IS_DYNAMIC;
+ } else {
+ query = String.format("%s %s",
+ AppSearchShortcutInfo.QUERY_IS_DYNAMIC,
+ AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER);
+ }
+ final boolean[] changed = new boolean[1];
+ forEachShortcutMutateIf(query, 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,45 +650,32 @@ 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.
+ // First, gather the pinned set from each launcher.
mShortcutUser.forAllLaunchers(launcherShortcuts -> {
final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
getPackageName(), getPackageUserId());
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);
- if (pinnedShortcuts.contains(si.getId()) && !si.isPinned()) {
- mutateShortcut(si.getId(), si,
- shortcut -> shortcut.addFlags(ShortcutInfo.FLAG_PINNED));
+ // Then, update the pinned state if necessary.
+ final List<ShortcutInfo> pinned = getShortcutById(pinnedShortcuts);
+ pinned.forEach(si -> {
+ if (!si.isPinned()) {
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
}
+ });
+ saveShortcut(pinned);
+ forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_IS_PINNED, si -> {
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();
@@ -734,9 +765,9 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* Find all shortcuts that match {@code query}.
*/
- public void findAll(@NonNull List<ShortcutInfo> result,
- @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
- findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
+ public void findAll(@NonNull List<ShortcutInfo> result, @Nullable String query,
+ @Nullable Predicate<ShortcutInfo> filter, int cloneFlag) {
+ findAll(result, query, filter, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
}
/**
@@ -747,7 +778,7 @@ class ShortcutPackage extends ShortcutPackageItem {
* adjusted for the caller too.
*/
public void findAll(@NonNull List<ShortcutInfo> result,
- @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
+ @Nullable String query, @Nullable Predicate<ShortcutInfo> filter, int cloneFlag,
@Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) {
if (getPackageInfo().isShadow()) {
// Restored and the app not installed yet, so don't return any.
@@ -759,38 +790,101 @@ class ShortcutPackage extends ShortcutPackageItem {
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
.getPinnedShortcutIds(getPackageName(), getPackageUserId());
+ forEachShortcut(query == null ? "" : query, si ->
+ filter(result, filter, cloneFlag, callingLauncher, pinnedByCallerSet,
+ getPinnedByAnyLauncher, si));
+ }
- for (int i = 0; i < mShortcuts.size(); i++) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- // 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.
- final boolean isPinnedByCaller = (callingLauncher == null)
- || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
-
- if (!getPinnedByAnyLauncher) {
- if (si.isFloating() && !si.isCached()) {
- if (!isPinnedByCaller) {
- continue;
- }
- }
- }
- final ShortcutInfo clone = si.clone(cloneFlag);
+ /**
+ * 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());
+ final List<ShortcutInfo> shortcuts = getShortcutById(ids);
+ for (ShortcutInfo si : shortcuts) {
+ filter(result, query, cloneFlag, callingLauncher, pinnedByCallerSet,
+ getPinnedByAnyLauncher, si);
+ }
+ }
- // Fix up isPinned for the caller. Note we need to do it before the "test" callback,
- // since it may check isPinned.
- // However, if getPinnedByAnyLauncher is set, we do it after the test.
- if (!getPinnedByAnyLauncher) {
+ /**
+ * Find all pinned shortcuts that match {@code query}.
+ */
+ public void findAllPinned(@NonNull List<ShortcutInfo> result,
+ @Nullable 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());
+ mShortcuts.values().forEach(si -> 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,
+ @NonNull final ArraySet<String> pinnedByCallerSet,
+ final boolean getPinnedByAnyLauncher, @NonNull final ShortcutInfo 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.
+ final boolean isPinnedByCaller = (callingLauncher == null)
+ || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
+
+ if (!getPinnedByAnyLauncher) {
+ if (si.isFloating() && !si.isCached()) {
if (!isPinnedByCaller) {
- clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+ return;
}
}
- if (query == null || query.test(clone)) {
- if (!isPinnedByCaller) {
- clone.clearFlags(ShortcutInfo.FLAG_PINNED);
- }
- result.add(clone);
+ }
+ final ShortcutInfo clone = si.clone(cloneFlag);
+
+ // Fix up isPinned for the caller. Note we need to do it before the "test" callback,
+ // since it may check isPinned.
+ // However, if getPinnedByAnyLauncher is set, we do it after the test.
+ if (!getPinnedByAnyLauncher) {
+ if (!isPinnedByCaller) {
+ clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+ }
+ }
+ if (query == null || query.test(clone)) {
+ if (!isPinnedByCaller) {
+ clone.clearFlags(ShortcutInfo.FLAG_PINNED);
}
+ result.add(clone);
}
}
@@ -822,8 +916,8 @@ class ShortcutPackage extends ShortcutPackageItem {
// Get the list of all dynamic shortcuts in this package.
final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
- findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
- ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
+ findAll(shortcuts, AppSearchShortcutInfo.QUERY_IS_NON_MANIFEST_VISIBLE,
+ ShortcutInfo::isNonManifestVisible, ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
for (int i = 0; i < shortcuts.size(); i++) {
@@ -861,14 +955,14 @@ class ShortcutPackage extends ShortcutPackageItem {
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) {
+ if (getShortcutCount() == 0 || mShareTargets.isEmpty()) {
return 0;
}
// Get the list of all dynamic shortcuts in this package
final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
- findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
- ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+ findAll(shortcuts, AppSearchShortcutInfo.QUERY_IS_NON_MANIFEST_VISIBLE,
+ ShortcutInfo::isNonManifestVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
int sharingShortcutCount = 0;
for (int i = 0; i < shortcuts.size(); i++) {
@@ -899,14 +993,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(AppSearchShortcutInfo.QUERY_HAS_BITMAP_PATH, si -> {
if (si.getBitmapPath() != null) {
usedFiles.add(getFileName(si.getBitmapPath()));
}
- }
+ });
return usedFiles;
}
@@ -923,30 +1015,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 +1120,34 @@ 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);
- if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
- continue;
- }
- 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;
- }
- 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);
- });
+ {
+ forEachShortcutMutateIf(
+ AppSearchShortcutInfo.QUERY_DISABLED_REASON_VERSION_LOWER, si -> {
+ if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+ 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()));
+ }
+ return false;
+ }
+ Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
+ si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+ return true;
+ });
}
// 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 +1160,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 +1205,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(AppSearchShortcutInfo.QUERY_IS_MANIFEST, si -> {
if (si.isManifestShortcut()) {
- if (toDisableList == null) {
- toDisableList = new ArraySet<>();
- }
toDisableList.add(si.getId());
}
- }
+ });
// Publish new ones.
if (newManifestShortcutList != null) {
@@ -1169,7 +1250,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 +1258,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 +1347,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(AppSearchShortcutInfo.QUERY_IS_NOT_FLOATING, 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 +1397,20 @@ 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);
+ final String query;
+ if (operation != ShortcutService.OPERATION_SET) {
+ query = AppSearchShortcutInfo.QUERY_IS_MANIFEST + " OR "
+ + AppSearchShortcutInfo.QUERY_IS_DYNAMIC;
+ } else {
+ query = AppSearchShortcutInfo.QUERY_IS_MANIFEST;
+ }
+ forEachShortcut(query, 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 +1423,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 +1462,17 @@ class ShortcutPackage extends ShortcutPackageItem {
public void resolveResourceStrings() {
final ShortcutService s = mShortcutUser.mService;
- List<ShortcutInfo> changedShortcuts = null;
-
- 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 publisherRes = getPackageResources();
+ final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
- 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(AppSearchShortcutInfo.QUERY_HAS_STRING_RESOURCE, 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 +1481,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 +1521,14 @@ 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);
- if (si.isFloating()) {
- if (si.getRank() != 0) {
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.setRank(0);
- });
- }
+ forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_IS_FLOATING_AND_HAS_RANK, si -> {
+ if (si.isFloating() && si.getRank() != 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 +1570,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(AppSearchShortcutInfo.QUERY_IS_NOT_MANIFEST, 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 +1617,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 +1627,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 +1650,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.
@@ -1638,10 +1692,10 @@ class ShortcutPackage extends ShortcutPackageItem {
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- final int size = getShortcutCount();
+ final int size = mShortcuts.size();
final int shareTargetSize = mShareTargets.size();
- if (size == 0 && shareTargetSize == 0 && mApiCallCount == 0) {
+ if (size == 0 && shareTargetSize == 0 && mApiCallCount == 0 && getShortcutCount() == 0) {
return; // nothing to write.
}
@@ -1650,11 +1704,19 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
+ if (!forBackup) {
+ /**
+ * Schema version should not be included in the backup because:
+ * 1. Schemas in AppSearch are created from scratch on new device
+ * 2. Shortcuts are restored from xml file (as opposed to from AppSearch) on new device
+ */
+ ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, (mIsInitilized)
+ ? AppSearchShortcutInfo.SCHEMA_VERSION : 0);
+ }
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup,
- getPackageInfo().isBackupAllowed());
+ saveShortcut(out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
}
if (!forBackup) {
@@ -1771,12 +1833,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());
@@ -1839,6 +1903,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName);
+ ret.mIsInitilized = ShortcutService.parseIntAttribute(parser, ATTR_SCHEMA_VERSON, 0) > 0;
ret.mApiCallCount =
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
ret.mLastResetTime =
@@ -2060,7 +2125,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 +2139,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 +2150,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 +2170,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 +2231,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 +2253,8 @@ class ShortcutPackage extends ShortcutPackageItem {
} else {
mPackageIdentifiers.remove(packageName);
}
+ awaitInAppSearch(true, "Update visibility",
+ session -> AndroidFuture.completedFuture(true));
}
void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
@@ -2196,23 +2264,309 @@ class ShortcutPackage extends ShortcutPackageItem {
synchronized (mLock) {
if (shortcut != null) {
transform.accept(shortcut);
+ }
+ final ShortcutInfo si = findShortcutById(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);
+ shortcuts.forEach(si -> {
+ if (si.isPinned()) {
+ mShortcuts.put(si.getId(), si);
} else {
- transform.accept(findShortcutById(id));
+ mShortcuts.remove(si.getId());
}
- // TODO: Load ShortcutInfo from AppSearch, apply transformation logic and save
+ });
+ saveToAppSearch(shortcuts);
+ }
+
+ private void saveToAppSearch(@NonNull final Collection<ShortcutInfo> shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ if (shortcuts.isEmpty()) {
+ // No need to invoke AppSearch when there's nothing to save.
+ return;
}
+ awaitInAppSearch("Saving shortcuts", 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;
+ });
}
/**
* Removes shortcuts from AppSearch.
*/
void removeShortcuts() {
+ awaitInAppSearch("Removing all shortcuts from " + getPackageName(), session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove("", getSearchSpec(), mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new RuntimeException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private void removeShortcut(@NonNull final String id) {
+ Objects.requireNonNull(id);
+ mShortcuts.remove(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;
+ });
+ }
+
+ @NonNull
+ private List<ShortcutInfo> getShortcutById(@NonNull final Collection<String> ids) {
+ final List<String> shortcutIds = new ArrayList<>(1);
+ for (String id : ids) {
+ if (id != null) {
+ shortcutIds.add(id);
+ }
+ }
+ return awaitInAppSearch("Getting shortcut by id", session -> {
+ final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
+ session.getByUri(
+ new GetByUriRequest.Builder(getPackageName()).addUris(shortcutIds).build(),
+ mShortcutUser.mExecutor,
+ results -> {
+ final List<ShortcutInfo> ret = new ArrayList<>(1);
+ Map<String, GenericDocument> documents = results.getSuccesses();
+ for (GenericDocument doc : documents.values()) {
+ final ShortcutInfo info = new AppSearchShortcutInfo(doc)
+ .toShortcutInfo(mShortcutUser.getUserId());
+ ret.add(info);
+ }
+ future.complete(ret);
+ });
+ return future;
+ });
+ }
+
+ private void forEachShortcut(@NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcut("", cb);
+ }
+
+ private void forEachShortcut(
+ @NonNull final String query, @NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutStopWhen(query, 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) {
+ forEachShortcutMutateIf("", cb);
+ }
+
+ private void forEachShortcutMutateIf(@NonNull final String query,
+ @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("Mutating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search(query, getSearchSpec())));
+ 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, getSearchSpec())));
+ 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 awaitInAppSearch(false, description, cb);
+ }
+
+ @Nullable
+ private <T> T awaitInAppSearch(
+ final boolean forceReset,
+ @NonNull final String description,
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ synchronized (mLock) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ final AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder(getPackageName()).build();
+ try (AppSearchSession session = ConcurrentUtils.waitForFutureNoInterrupt(
+ mShortcutUser.getAppSearch(searchContext), "Resetting app search")) {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
+ .build());
+ final boolean wasInitialized = mIsInitilized;
+ if (!wasInitialized || forceReset) {
+ ConcurrentUtils.waitForFutureNoInterrupt(
+ setupSchema(session), "Setting up schema");
+ }
+ mIsInitilized = true;
+ if (!wasInitialized) {
+ restoreParsedShortcuts(false);
+ }
+ return ConcurrentUtils.waitForFutureNoInterrupt(cb.apply(session), description);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initiate app search for shortcut package "
+ + getPackageName() + " user " + mShortcutUser.getUserId(), e);
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+ }
+
+ @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;
+ }
+
+ @NonNull
+ private SearchSpec getSearchSpec() {
+ return new SearchSpec.Builder()
+ .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
+ .addFilterNamespaces(getPackageName())
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build();
}
/**
- * Merge/replace shortcuts parsed from xml file.
+ * Replace shortcuts parsed from xml file.
*/
- void restoreParsedShortcuts(final boolean replace) {
+ void restoreParsedShortcuts() {
+ restoreParsedShortcuts(true);
+ }
+
+ private void restoreParsedShortcuts(final boolean replace) {
+ if (replace) {
+ removeShortcuts();
+ }
+ saveToAppSearch(mShortcuts.values());
}
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..dcf730d4a0fd 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -39,6 +39,7 @@ import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
+import android.content.pm.AppSearchShortcutInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.IPackageManager;
@@ -1050,8 +1051,7 @@ public class ShortcutService extends IShortcutService.Stub {
file.failWrite(os);
}
- final ShortcutUser user = getUserShortcutsLocked(userId);
- user.logSharingShortcutStats(mMetricsLogger);
+ getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
}
@GuardedBy("mLock")
@@ -1306,7 +1306,7 @@ public class ShortcutService extends IShortcutService.Stub {
mUsers.put(userId, userPackages);
// Also when a user's data is first accessed, scan all packages.
- checkPackageChanges(userId);
+ injectPostToHandler(() -> checkPackageChanges(userId));
}
return userPackages;
}
@@ -1934,7 +1934,8 @@ public class ShortcutService extends IShortcutService.Stub {
}
ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>();
- ps.findAll(cachedOrPinned, (ShortcutInfo si) -> si.isVisibleToPublisher()
+ ps.findAll(cachedOrPinned, AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED,
+ (ShortcutInfo si) -> si.isVisibleToPublisher()
&& si.isDynamic() && (si.isCached() || si.isPinned()),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
@@ -2431,8 +2432,9 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
// Dynamic shortcuts that are either cached or pinned will not get deleted.
- ps.findAll(changedShortcuts, (ShortcutInfo si) -> si.isVisibleToPublisher()
- && si.isDynamic() && (si.isCached() || si.isPinned()),
+ ps.findAll(changedShortcuts, AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED,
+ (ShortcutInfo si) -> si.isVisibleToPublisher()
+ && si.isDynamic() && (si.isCached() || si.isPinned()),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
@@ -2511,8 +2513,11 @@ public class ShortcutService extends IShortcutService.Stub {
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
| (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
+ final String query = AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER + " "
+ + createQuery(matchDynamic, matchPinned, matchManifest, matchCached);
+
callback.complete(getShortcutsWithQueryLocked(
- packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
+ packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, query,
(ShortcutInfo si) ->
si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0));
}
@@ -2588,12 +2593,13 @@ public class ShortcutService extends IShortcutService.Stub {
@GuardedBy("mLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
- @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
+ @UserIdInt int userId, int cloneFlags, @NonNull final String query,
+ @NonNull Predicate<ShortcutInfo> filter) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.findAll(ret, query, cloneFlags);
+ ps.findAll(ret, query, filter, cloneFlags);
return new ParceledListSlice<>(setReturnedByServer(ret));
}
@@ -2851,6 +2857,28 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
+ private String createQuery(final boolean matchDynamic, final boolean matchPinned,
+ final boolean matchManifest, final boolean matchCached) {
+
+ final List<String> queries = new ArrayList<>(1);
+ if (matchDynamic) {
+ queries.add(AppSearchShortcutInfo.QUERY_IS_DYNAMIC);
+ }
+ if (matchPinned) {
+ queries.add(AppSearchShortcutInfo.QUERY_IS_PINNED);
+ }
+ if (matchManifest) {
+ queries.add(AppSearchShortcutInfo.QUERY_IS_MANIFEST);
+ }
+ if (matchCached) {
+ queries.add(AppSearchShortcutInfo.QUERY_IS_CACHED);
+ }
+ if (queries.isEmpty()) {
+ return "";
+ }
+ return "(" + String.join(" OR ", queries) + ")";
+ }
+
/**
* Remove all the information associated with a package. This will really remove all the
* information, including the restore information (i.e. it'll remove packages even if they're
@@ -2963,18 +2991,12 @@ public class ShortcutService extends IShortcutService.Stub {
int callingPid, int callingUid) {
final ArraySet<String> ids = shortcutIds == null ? null
: new ArraySet<>(shortcutIds);
- final ArraySet<LocusId> locIds = locusIds == null ? null
- : new ArraySet<>(locusIds);
final ShortcutUser user = getUserShortcutsLocked(userId);
final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName);
if (p == null) {
return; // No need to instantiate ShortcutPackage.
}
- final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
- final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
- final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
- final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0;
final boolean canAccessAllShortcuts =
canSeeAnyPinnedShortcut(callingPackage, launcherUserId, callingPid, callingUid);
@@ -2982,38 +3004,73 @@ public class ShortcutService extends IShortcutService.Stub {
final boolean getPinnedByAnyLauncher =
canAccessAllShortcuts &&
((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0);
+ queryFlags |= (getPinnedByAnyLauncher ? ShortcutQuery.FLAG_MATCH_PINNED : 0);
+
+ final boolean matchPinnedOnly =
+ ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0)
+ && ((queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) == 0)
+ && ((queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) == 0)
+ && ((queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) == 0);
+
+ final Predicate<ShortcutInfo> filter = getFilterFromQuery(ids, locusIds, changedSince,
+ componentName, queryFlags, getPinnedByAnyLauncher);
+ if (matchPinnedOnly) {
+ p.findAllPinned(ret, filter, cloneFlag, callingPackage, launcherUserId,
+ getPinnedByAnyLauncher);
+ } else if (ids != null && !ids.isEmpty()) {
+ p.findAllByIds(ret, ids, filter, cloneFlag, callingPackage, launcherUserId,
+ getPinnedByAnyLauncher);
+ } else {
+ final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
+ final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
+ final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+ final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0;
+ p.findAll(ret, createQuery(matchDynamic, matchPinned, matchManifest, matchCached),
+ filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher);
+ }
+ }
- p.findAll(ret,
- (ShortcutInfo si) -> {
- if (si.getLastChangedTimestamp() < changedSince) {
- return false;
- }
- if (ids != null && !ids.contains(si.getId())) {
- return false;
- }
- if (locIds != null && !locIds.contains(si.getLocusId())) {
- return false;
- }
- if (componentName != null) {
- if (si.getActivity() != null
- && !si.getActivity().equals(componentName)) {
- return false;
- }
- }
- if (matchDynamic && si.isDynamic()) {
- return true;
- }
- if ((matchPinned || getPinnedByAnyLauncher) && si.isPinned()) {
- return true;
- }
- if (matchManifest && si.isDeclaredInManifest()) {
- return true;
- }
- if (matchCached && si.isCached()) {
- return true;
- }
+ private Predicate<ShortcutInfo> getFilterFromQuery(@Nullable ArraySet<String> ids,
+ @Nullable List<LocusId> locusIds, long changedSince,
+ @Nullable ComponentName componentName, int queryFlags,
+ boolean getPinnedByAnyLauncher) {
+ final ArraySet<LocusId> locIds = locusIds == null ? null
+ : new ArraySet<>(locusIds);
+
+ final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
+ final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
+ final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+ final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0;
+ return si -> {
+ if (si.getLastChangedTimestamp() < changedSince) {
+ return false;
+ }
+ if (ids != null && !ids.contains(si.getId())) {
+ return false;
+ }
+ if (locIds != null && !locIds.contains(si.getLocusId())) {
+ return false;
+ }
+ if (componentName != null) {
+ if (si.getActivity() != null
+ && !si.getActivity().equals(componentName)) {
return false;
- }, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher);
+ }
+ }
+ if (matchDynamic && si.isDynamic()) {
+ return true;
+ }
+ if ((matchPinned || getPinnedByAnyLauncher) && si.isPinned()) {
+ return true;
+ }
+ if (matchManifest && si.isDeclaredInManifest()) {
+ return true;
+ }
+ if (matchCached && si.isCached()) {
+ return true;
+ }
+ return false;
+ };
}
@Override
@@ -3054,7 +3111,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
- p.findAll(list,
+ p.findAllByIds(list, Collections.singletonList(shortcutId),
(ShortcutInfo si) -> shortcutId.equals(si.getId()),
/* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher);
return list.size() == 0 ? null : list.get(0);
@@ -3084,9 +3141,11 @@ public class ShortcutService extends IShortcutService.Stub {
if (sp != null) {
// List the shortcuts that are pinned only, these will get removed.
removedShortcuts = new ArrayList<>();
- sp.findAll(removedShortcuts, (ShortcutInfo si) -> si.isVisibleToPublisher()
- && si.isPinned() && !si.isCached() && !si.isDynamic()
- && !si.isDeclaredInManifest(), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO,
+ sp.findAll(removedShortcuts, AppSearchShortcutInfo.QUERY_IS_VISIBLE_PINNED_ONLY,
+ (ShortcutInfo si) -> si.isVisibleToPublisher()
+ && si.isPinned() && !si.isCached() && !si.isDynamic()
+ && !si.isDeclaredInManifest(),
+ ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO,
callingPackage, launcherUserId, false);
}
// Get list of shortcuts that will get unpinned.
@@ -5081,6 +5140,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);
@@ -5162,7 +5232,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
List<ShortcutInfo> result = new ArrayList<>();
- ps.findAll(result, (ShortcutInfo si) -> resultIds.contains(si.getId()),
+ ps.findAllByIds(result, resultIds, (ShortcutInfo si) -> resultIds.contains(si.getId()),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
return result;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 5f0aa03cac59..069944d955ea 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -457,7 +457,6 @@ class ShortcutUser {
case ShortcutPackage.TAG_ROOT: {
final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
s, ret, parser, fromBackup);
- shortcuts.restoreParsedShortcuts(false);
// Don't use addShortcut(), we don't need to save the icon.
ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
@@ -492,7 +491,6 @@ class ShortcutUser {
final ShortcutPackage sp = ShortcutPackage.loadFromFile(s, ret, f, fromBackup);
if (sp != null) {
ret.mPackages.put(sp.getPackageName(), sp);
- sp.restoreParsedShortcuts(false);
}
});
@@ -575,7 +573,7 @@ class ShortcutUser {
Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored."
+ " Existing non-manifeset shortcuts will be overwritten.");
}
- sp.restoreParsedShortcuts(true);
+ sp.restoreParsedShortcuts();
addPackage(sp);
restoredPackages[0]++;
restoredShortcuts[0] += sp.getShortcutCount();
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 bbcf527a0af7..198fb4f5d295 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -2042,6 +2042,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);
}
@@ -2237,6 +2242,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, () -> {
@@ -2396,6 +2405,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"));