From 47d1a022674e3e8c8db21eadfa51b9ea28b79aed Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Thu, 11 Feb 2021 13:00:59 -0800 Subject: Shortcut integration with AppSearch (Part 2) In order to move shortcuts from memory into AppSearch, we need to move the bookkeeping states associated with ShortcutInfo into AppSearch as well. This CL updates the schema to reflect the necessary chagnes. Bug: 151359749 Test: atest ShortcutManagerTest1 ShortcutManagerTest2 ShortcutManagerTest3 ShortcutManagerTest4 ShortcutManagerTest5 ShortcutManagerTest6 ShortcutManagerTest7 ShortcutManagerTest8 ShortcutManagerTest9 ShortcutManagerTest10 ShortcutManagerTest11 Change-Id: Iaafaf6b81504b3f80c05b7f4a996b1b5b6beaeb7 --- core/java/android/content/pm/AppSearchPerson.java | 2 +- .../android/content/pm/AppSearchShortcutInfo.java | 229 ++++++++++++++++----- core/java/android/content/pm/ShortcutInfo.java | 6 +- .../content/pm/AppSearchShortcutInfoTest.java | 5 +- 4 files changed, 186 insertions(+), 56 deletions(-) diff --git a/core/java/android/content/pm/AppSearchPerson.java b/core/java/android/content/pm/AppSearchPerson.java index d70ac918e208..66295eb513d8 100644 --- a/core/java/android/content/pm/AppSearchPerson.java +++ b/core/java/android/content/pm/AppSearchPerson.java @@ -42,7 +42,7 @@ public class AppSearchPerson extends GenericDocument { public static final String KEY_IS_BOT = "isBot"; public static final String KEY_IS_IMPORTANT = "isImportant"; - private AppSearchPerson(@NonNull GenericDocument document) { + public AppSearchPerson(@NonNull GenericDocument document) { super(document); } diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index ebe202b2a3fa..5af3b5a01d07 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -28,7 +28,6 @@ import android.content.LocusId; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.PersistableBundle; -import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; @@ -53,17 +52,23 @@ 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 String KEY_PACKAGE_NAME = "packageName"; public static final String KEY_ACTIVITY = "activity"; - public static final String KEY_TITLE = "title"; - public static final String KEY_TEXT = "text"; + public static final String KEY_SHORT_LABEL = "shortLabel"; + public static final String KEY_SHORT_LABEL_RES_ID = "shortLabelResId"; + public static final String KEY_SHORT_LABEL_RES_NAME = "shortLabelResName"; + public static final String KEY_LONG_LABEL = "longLabel"; + public static final String KEY_LONG_LABEL_RES_ID = "longLabelResId"; + public static final String KEY_LONG_LABEL_RES_NAME = "longLabelResName"; public static final String KEY_DISABLED_MESSAGE = "disabledMessage"; + public static final String KEY_DISABLED_MESSAGE_RES_ID = "disabledMessageResId"; + public static final String KEY_DISABLED_MESSAGE_RES_NAME = "disabledMessageResName"; public static final String KEY_CATEGORIES = "categories"; public static final String KEY_INTENTS = "intents"; public static final String KEY_INTENT_PERSISTABLE_EXTRAS = "intentPersistableExtras"; public static final String KEY_PERSON = "person"; public static final String KEY_LOCUS_ID = "locusId"; public static final String KEY_RANK = "rank"; + public static final String KEY_IMPLICIT_RANK = "implicitRank"; public static final String KEY_EXTRAS = "extras"; public static final String KEY_FLAGS = "flags"; public static final String KEY_ICON_RES_ID = "iconResId"; @@ -73,36 +78,62 @@ public class AppSearchShortcutInfo extends GenericDocument { public static final String KEY_DISABLED_REASON = "disabledReason"; public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE) - .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_PACKAGE_NAME) - .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ACTIVITY) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS) .build() - ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ACTIVITY) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_SHORT_LABEL) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build() - ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_TITLE) + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_SHORT_LABEL_RES_ID) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build() - ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_TEXT) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_SHORT_LABEL_RES_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .build() + + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LONG_LABEL) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build() + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_LONG_LABEL_RES_ID) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .build() + + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LONG_LABEL_RES_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .build() + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_DISABLED_MESSAGE) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder( + KEY_DISABLED_MESSAGE_RES_ID) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .build() + + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder( + KEY_DISABLED_MESSAGE_RES_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .build() + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_CATEGORIES) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) @@ -135,6 +166,10 @@ public class AppSearchShortcutInfo extends GenericDocument { .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .build() + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_IMPLICIT_RANK) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .build() + ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(KEY_EXTRAS) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .build() @@ -183,13 +218,21 @@ public class AppSearchShortcutInfo extends GenericDocument { Objects.requireNonNull(shortcutInfo); return new Builder(shortcutInfo.getId()) .setActivity(shortcutInfo.getActivity()) - .setPackageName(shortcutInfo.getPackage()) - .setTitle(shortcutInfo.getShortLabel()) - .setText(shortcutInfo.getLongLabel()) + .setNamespace(shortcutInfo.getPackage()) + .setShortLabel(shortcutInfo.getShortLabel()) + .setShortLabelResId(shortcutInfo.getShortLabelResourceId()) + .setShortLabelResName(shortcutInfo.getTitleResName()) + .setLongLabel(shortcutInfo.getLongLabel()) + .setLongLabelResId(shortcutInfo.getLongLabelResourceId()) + .setLongLabelResName(shortcutInfo.getTextResName()) .setDisabledMessage(shortcutInfo.getDisabledMessage()) + .setDisabledMessageResId(shortcutInfo.getDisabledMessageResourceId()) + .setDisabledMessageResName(shortcutInfo.getDisabledMessageResName()) .setCategories(shortcutInfo.getCategories()) .setIntents(shortcutInfo.getIntents()) .setRank(shortcutInfo.getRank()) + .setImplicitRank(shortcutInfo.getImplicitRank() + | (shortcutInfo.isRankChanged() ? ShortcutInfo.RANK_CHANGED_BIT : 0)) .setExtras(shortcutInfo.getExtras()) .setCreationTimestampMillis(shortcutInfo.getLastChangedTimestamp()) .setFlags(shortcutInfo.getFlags()) @@ -207,17 +250,8 @@ public class AppSearchShortcutInfo extends GenericDocument { * @hide */ @NonNull - public ShortcutInfo toShortcutInfo() { - return toShortcutInfo(UserHandle.myUserId()); - } - - /** - * @hide - * TODO: This should be @SystemApi when AppSearchShortcutInfo unhides. - */ - @NonNull - public ShortcutInfo toShortcutInfo(@UserIdInt final int userId) { - final String packageName = getPropertyString(KEY_PACKAGE_NAME); + public ShortcutInfo toShortcutInfo(@UserIdInt int userId) { + final String packageName = getNamespace(); final String activityString = getPropertyString(KEY_ACTIVITY); final ComponentName activity = activityString == null ? null : ComponentName.unflattenFromString(activityString); @@ -228,15 +262,24 @@ public class AppSearchShortcutInfo extends GenericDocument { // @hide and @UnsupportedAppUsage, we could migrate existing usage in platform with // LauncherApps#getShortcutIconDrawable instead. final Icon icon = null; - final String title = getPropertyString(KEY_TITLE); - final String text = getPropertyString(KEY_TEXT); + final String shortLabel = getPropertyString(KEY_SHORT_LABEL); + final int shortLabelResId = (int) getPropertyLong(KEY_SHORT_LABEL_RES_ID); + final String shortLabelResName = getPropertyString(KEY_SHORT_LABEL_RES_NAME); + final String longLabel = getPropertyString(KEY_LONG_LABEL); + final int longLabelResId = (int) getPropertyLong(KEY_LONG_LABEL_RES_ID); + final String longLabelResName = getPropertyString(KEY_LONG_LABEL_RES_NAME); final String disabledMessage = getPropertyString(KEY_DISABLED_MESSAGE); + final int disabledMessageResId = (int) getPropertyLong(KEY_DISABLED_MESSAGE_RES_ID); + final String disabledMessageResName = getPropertyString(KEY_DISABLED_MESSAGE_RES_NAME); final String[] categories = getPropertyStringArray(KEY_CATEGORIES); final Set categoriesSet = categories == null - ? new ArraySet<>() : new ArraySet<>(Arrays.asList(categories)); + ? null : new ArraySet<>(Arrays.asList(categories)); final String[] intentsStrings = getPropertyStringArray(KEY_INTENTS); final Intent[] intents = intentsStrings == null - ? null : Arrays.stream(intentsStrings).map(uri -> { + ? new Intent[0] : Arrays.stream(intentsStrings).map(uri -> { + if (TextUtils.isEmpty(uri)) { + return new Intent(Intent.ACTION_VIEW); + } try { return Intent.parseUri(uri, /* flags =*/ 0); } catch (URISyntaxException e) { @@ -251,15 +294,18 @@ public class AppSearchShortcutInfo extends GenericDocument { if (intents != null) { for (int i = 0; i < intents.length; i++) { final Intent intent = intents[i]; - if (intent != null) { - intent.replaceExtras(intentExtrases[i].size() == 0 ? null : intentExtrases[i]); + if (intent == null || intentExtrases == null || intentExtrases.length <= i + || intentExtrases[i] == null || intentExtrases[i].size() == 0) { + continue; } + intent.replaceExtras(intentExtrases[i]); } } 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 implicitRank = (int) getPropertyLong(KEY_IMPLICIT_RANK); final byte[] extrasByte = getPropertyBytes(KEY_EXTRAS); final PersistableBundle extras = transformToPersistableBundle(extrasByte); final int flags = parseFlags(getPropertyLongArray(KEY_FLAGS)); @@ -268,12 +314,17 @@ public class AppSearchShortcutInfo extends GenericDocument { final String iconUri = getPropertyString(KEY_ICON_URI); final String bitmapPath = getPropertyString(KEY_BITMAP_PATH); final int disabledReason = (int) getPropertyLong(KEY_DISABLED_REASON); - return new ShortcutInfo( - userId, getUri(), packageName, activity, icon, title, 0, null, - text, 0, null, disabledMessage, 0, null, - categoriesSet, intents, rank, extras, + final ShortcutInfo si = new ShortcutInfo( + userId, getUri(), packageName, activity, icon, shortLabel, shortLabelResId, + shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage, + disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras, getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri, disabledReason, persons, locusId, 0); + si.setImplicitRank(implicitRank); + if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) { + si.setRankChanged(); + } + return si; } /** @hide */ @@ -310,9 +361,9 @@ public class AppSearchShortcutInfo extends GenericDocument { * @hide */ @NonNull - public Builder setTitle(@Nullable final CharSequence shortLabel) { + public Builder setShortLabel(@Nullable final CharSequence shortLabel) { if (!TextUtils.isEmpty(shortLabel)) { - setPropertyString(KEY_TITLE, Preconditions.checkStringNotEmpty( + setPropertyString(KEY_SHORT_LABEL, Preconditions.checkStringNotEmpty( shortLabel, "shortLabel cannot be empty").toString()); } return this; @@ -322,13 +373,50 @@ public class AppSearchShortcutInfo extends GenericDocument { * @hide */ @NonNull - public Builder setText(@Nullable final CharSequence longLabel) { + public Builder setShortLabelResId(@Nullable final int shortLabelResId) { + setPropertyLong(KEY_SHORT_LABEL_RES_ID, shortLabelResId); + return this; + } + + /** + * @hide + */ + public Builder setShortLabelResName(@Nullable final String shortLabelResName) { + if (!TextUtils.isEmpty(shortLabelResName)) { + setPropertyString(KEY_SHORT_LABEL_RES_NAME, shortLabelResName); + } + return this; + } + + /** + * @hide + */ + @NonNull + public Builder setLongLabel(@Nullable final CharSequence longLabel) { if (!TextUtils.isEmpty(longLabel)) { - setPropertyString(KEY_TEXT, Preconditions.checkStringNotEmpty( + setPropertyString(KEY_LONG_LABEL, Preconditions.checkStringNotEmpty( longLabel, "longLabel cannot be empty").toString()); } return this; + } + + /** + * @hide + */ + @NonNull + public Builder setLongLabelResId(@Nullable final int longLabelResId) { + setPropertyLong(KEY_LONG_LABEL_RES_ID, longLabelResId); + return this; + } + /** + * @hide + */ + public Builder setLongLabelResName(@Nullable final String longLabelResName) { + if (!TextUtils.isEmpty(longLabelResName)) { + setPropertyString(KEY_LONG_LABEL_RES_NAME, longLabelResName); + } + return this; } /** @@ -343,6 +431,25 @@ public class AppSearchShortcutInfo extends GenericDocument { return this; } + /** + * @hide + */ + @NonNull + public Builder setDisabledMessageResId(@Nullable final int disabledMessageResId) { + setPropertyLong(KEY_DISABLED_MESSAGE_RES_ID, disabledMessageResId); + return this; + } + + /** + * @hide + */ + public Builder setDisabledMessageResName(@Nullable final String disabledMessageResName) { + if (!TextUtils.isEmpty(disabledMessageResName)) { + setPropertyString(KEY_DISABLED_MESSAGE_RES_NAME, disabledMessageResName); + } + return this; + } + /** * @hide */ @@ -384,9 +491,8 @@ public class AppSearchShortcutInfo extends GenericDocument { intentExtrases[i] = extras == null ? new byte[0] : transformToByteArray(new PersistableBundle(extras)); } - - setPropertyString(KEY_INTENTS, Arrays.stream(intents).map(it -> - it.toUri(0)).toArray(String[]::new)); + setPropertyString(KEY_INTENTS, Arrays.stream(intents).map(it -> it.toUri(0)) + .toArray(String[]::new)); setPropertyBytes(KEY_INTENT_PERSISTABLE_EXTRAS, intentExtrases); return this; } @@ -410,10 +516,14 @@ public class AppSearchShortcutInfo extends GenericDocument { if (persons == null || persons.length == 0) { return this; } - setPropertyDocument(KEY_PERSON, - Arrays.stream(persons).map(person -> AppSearchPerson.instance( - Objects.requireNonNull(person, "persons cannot contain null")) - ).toArray(AppSearchPerson[]::new)); + final GenericDocument[] documents = new GenericDocument[persons.length]; + for (int i = 0; i < persons.length; i++) { + final Person person = persons[i]; + if (person == null) continue; + final AppSearchPerson appSearchPerson = AppSearchPerson.instance(person); + documents[i] = appSearchPerson; + } + setPropertyDocument(KEY_PERSON, documents); return this; } @@ -422,12 +532,20 @@ public class AppSearchShortcutInfo extends GenericDocument { */ @NonNull public Builder setRank(final int rank) { - Preconditions.checkArgument((0 <= rank), - "Rank cannot be negative or bigger than MAX_RANK"); + Preconditions.checkArgument((0 <= rank), "Rank cannot be negative"); setPropertyLong(KEY_RANK, rank); return this; } + /** + * @hide + */ + @NonNull + public Builder setImplicitRank(final int rank) { + setPropertyLong(KEY_IMPLICIT_RANK, rank); + return this; + } + /** * @hide */ @@ -444,7 +562,7 @@ public class AppSearchShortcutInfo extends GenericDocument { */ public Builder setPackageName(@Nullable final String packageName) { if (!TextUtils.isEmpty(packageName)) { - setPropertyString(KEY_PACKAGE_NAME, packageName); + setNamespace(packageName); } return this; } @@ -579,7 +697,14 @@ public class AppSearchShortcutInfo extends GenericDocument { @NonNull private static Person[] parsePerson(@Nullable final GenericDocument[] persons) { - return persons == null ? new Person[0] : Arrays.stream(persons).map(it -> - ((AppSearchPerson) it).toPerson()).toArray(Person[]::new); + if (persons == null) return new Person[0]; + final Person[] ret = new Person[persons.length]; + for (int i = 0; i < persons.length; i++) { + final GenericDocument document = persons[i]; + if (document == null) continue; + final AppSearchPerson person = new AppSearchPerson(document); + ret[i] = person.toPerson(); + } + return ret; } } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 5f80ba110773..275e81c0e868 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -68,7 +68,8 @@ public final class ShortcutInfo implements Parcelable { private static final int IMPLICIT_RANK_MASK = 0x7fffffff; - private static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK; + /** @hide */ + public static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK; /** @hide */ public static final int RANK_NOT_SET = Integer.MAX_VALUE; @@ -1595,6 +1596,9 @@ public final class ShortcutInfo implements Parcelable { */ @Nullable public Intent[] getIntents() { + if (mIntents == null) { + return null; + } final Intent[] ret = new Intent[mIntents.length]; for (int i = 0; i < ret.length; i++) { diff --git a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java index da92e69b6378..ffe93bc99cf5 100644 --- a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java +++ b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java @@ -47,15 +47,16 @@ public class AppSearchShortcutInfoTest { final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW); final ShortcutInfo shortcut = new AppSearchShortcutInfo.Builder(id) .setActivity(activity) - .setText(id) + .setLongLabel(id) .setIconResName(shortcutIconResName) .setIntent(shortcutIntent) .setPerson(person) .setCategories(categorySet) .setFlags(ShortcutInfo.FLAG_LONG_LIVED) .build() - .toShortcutInfo(); + .toShortcutInfo(0); + assertThat(shortcut.getUserId()).isEqualTo(0); assertThat(shortcut.getId()).isEqualTo(id); assertThat(shortcut.getShortLabel()).isEqualTo(id); assertThat(shortcut.getIconResName()).isEqualTo(shortcutIconResName); -- cgit v1.2.3-59-g8ed1b