From df6da040e00cba255cad64d2d231aae62928607a Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Thu, 16 Jun 2016 09:51:40 -0700 Subject: [PATCH] Update manifest shortcut XML schema Use the standard tag instead of custom tags. - Also fix setDynamicShortcuts(), which was broken in the previous CL. - Also tolerate runtime exceptions while parsing XMLs - Also fix b/29422018 while I'm at it Bug 29390156 Bug 29077932 Bug 29422018 Change-Id: I2756c9d66c6d7b2962a982d9e57a7d84a5755b28 --- api/current.txt | 24 +- api/system-current.txt | 24 +- api/test-current.txt | 24 +- .../java/android/content/pm/ShortcutInfo.java | 63 ++++- core/res/res/values/attrs.xml | 9 +- core/res/res/values/public.xml | 4 - .../android/server/pm/ShortcutPackage.java | 4 +- .../com/android/server/pm/ShortcutParser.java | 187 +++++++++----- .../android/server/pm/ShortcutService.java | 4 + .../servicestests/res/drawable/icon3.png | Bin 0 -> 5810 bytes .../servicestests/res/xml/shortcut_1.xml | 15 +- .../servicestests/res/xml/shortcut_1_alt.xml | 15 +- .../res/xml/shortcut_1_disable.xml | 2 +- .../servicestests/res/xml/shortcut_2.xml | 29 ++- .../res/xml/shortcut_2_duplicate.xml | 16 +- .../servicestests/res/xml/shortcut_5.xml | 64 +++-- .../servicestests/res/xml/shortcut_5_alt.xml | 51 ++-- .../res/xml/shortcut_5_reverse.xml | 55 +++-- .../res/xml/shortcut_error_1.xml | 16 +- .../res/xml/shortcut_error_2.xml | 16 +- .../res/xml/shortcut_error_3.xml | 8 +- .../res/xml/shortcut_error_4.xml | 63 +++++ .../server/pm/ShortcutManagerTest1.java | 233 ++++++++++++++---- .../ShortcutManagerTestUtils.java | 140 ++++++++++- 24 files changed, 810 insertions(+), 256 deletions(-) create mode 100644 services/tests/servicestests/res/drawable/icon3.png create mode 100644 services/tests/servicestests/res/xml/shortcut_error_4.xml diff --git a/api/current.txt b/api/current.txt index ae6a8bc7bc0c3..57f1f65253f20 100644 --- a/api/current.txt +++ b/api/current.txt @@ -420,9 +420,9 @@ package android { field public static final int contentInsetStart = 16843859; // 0x1010453 field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522 field public static final int contextClickable = 16844007; // 0x10104e7 - field public static final int contextDescription = 16844082; // 0x1010532 + field public static final int contextDescription = 16844078; // 0x101052e field public static final int contextPopupMenuStyle = 16844033; // 0x1010501 - field public static final int contextUri = 16844081; // 0x1010531 + field public static final int contextUri = 16844077; // 0x101052d field public static final int controlX1 = 16843772; // 0x10103fc field public static final int controlX2 = 16843774; // 0x10103fe field public static final int controlY1 = 16843773; // 0x10103fd @@ -1041,7 +1041,7 @@ package android { field public static final int rotation = 16843558; // 0x1010326 field public static final int rotationX = 16843559; // 0x1010327 field public static final int rotationY = 16843560; // 0x1010328 - field public static final int roundIcon = 16844080; // 0x1010530 + field public static final int roundIcon = 16844076; // 0x101052c field public static final int rowCount = 16843637; // 0x1010375 field public static final int rowDelay = 16843216; // 0x10101d0 field public static final int rowEdgeFlags = 16843329; // 0x1010241 @@ -1109,20 +1109,16 @@ package android { field public static final int shareInterpolator = 16843195; // 0x10101bb field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserLabel = 16843361; // 0x1010261 - field public static final int shortcutCategories = 16844077; // 0x101052d - field public static final int shortcutDisabledMessage = 16844076; // 0x101052c - field public static final int shortcutIcon = 16844073; // 0x1010529 + field public static final int shortcutDisabledMessage = 16844075; // 0x101052b field public static final int shortcutId = 16844072; // 0x1010528 - field public static final int shortcutIntentAction = 16844078; // 0x101052e - field public static final int shortcutIntentData = 16844079; // 0x101052f - field public static final int shortcutLongLabel = 16844075; // 0x101052b - field public static final int shortcutShortLabel = 16844074; // 0x101052a + field public static final int shortcutLongLabel = 16844074; // 0x101052a + field public static final int shortcutShortLabel = 16844073; // 0x1010529 field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 field public static final int showForAllUsers = 16844015; // 0x10104ef - field public static final int showMetadataInPreview = 16844083; // 0x1010533 + field public static final int showMetadataInPreview = 16844079; // 0x101052f field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9 field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad @@ -10099,14 +10095,14 @@ package android.content.pm { method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName); method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set); - method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); - method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setRank(int); - method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.CharSequence); } public class ShortcutManager { diff --git a/api/system-current.txt b/api/system-current.txt index 15e8673638cdf..0f37751021b87 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -527,9 +527,9 @@ package android { field public static final int contentInsetStart = 16843859; // 0x1010453 field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522 field public static final int contextClickable = 16844007; // 0x10104e7 - field public static final int contextDescription = 16844082; // 0x1010532 + field public static final int contextDescription = 16844078; // 0x101052e field public static final int contextPopupMenuStyle = 16844033; // 0x1010501 - field public static final int contextUri = 16844081; // 0x1010531 + field public static final int contextUri = 16844077; // 0x101052d field public static final int controlX1 = 16843772; // 0x10103fc field public static final int controlX2 = 16843774; // 0x10103fe field public static final int controlY1 = 16843773; // 0x10103fd @@ -1148,7 +1148,7 @@ package android { field public static final int rotation = 16843558; // 0x1010326 field public static final int rotationX = 16843559; // 0x1010327 field public static final int rotationY = 16843560; // 0x1010328 - field public static final int roundIcon = 16844080; // 0x1010530 + field public static final int roundIcon = 16844076; // 0x101052c field public static final int rowCount = 16843637; // 0x1010375 field public static final int rowDelay = 16843216; // 0x10101d0 field public static final int rowEdgeFlags = 16843329; // 0x1010241 @@ -1220,20 +1220,16 @@ package android { field public static final int shareInterpolator = 16843195; // 0x10101bb field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserLabel = 16843361; // 0x1010261 - field public static final int shortcutCategories = 16844077; // 0x101052d - field public static final int shortcutDisabledMessage = 16844076; // 0x101052c - field public static final int shortcutIcon = 16844073; // 0x1010529 + field public static final int shortcutDisabledMessage = 16844075; // 0x101052b field public static final int shortcutId = 16844072; // 0x1010528 - field public static final int shortcutIntentAction = 16844078; // 0x101052e - field public static final int shortcutIntentData = 16844079; // 0x101052f - field public static final int shortcutLongLabel = 16844075; // 0x101052b - field public static final int shortcutShortLabel = 16844074; // 0x101052a + field public static final int shortcutLongLabel = 16844074; // 0x101052a + field public static final int shortcutShortLabel = 16844073; // 0x1010529 field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 field public static final int showForAllUsers = 16844015; // 0x10104ef - field public static final int showMetadataInPreview = 16844083; // 0x1010533 + field public static final int showMetadataInPreview = 16844079; // 0x101052f field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9 field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad @@ -10523,14 +10519,14 @@ package android.content.pm { method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName); method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set); - method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); - method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setRank(int); - method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.CharSequence); } public class ShortcutManager { diff --git a/api/test-current.txt b/api/test-current.txt index 4bb7a79c932b2..ec10ccc8c2519 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -420,9 +420,9 @@ package android { field public static final int contentInsetStart = 16843859; // 0x1010453 field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522 field public static final int contextClickable = 16844007; // 0x10104e7 - field public static final int contextDescription = 16844082; // 0x1010532 + field public static final int contextDescription = 16844078; // 0x101052e field public static final int contextPopupMenuStyle = 16844033; // 0x1010501 - field public static final int contextUri = 16844081; // 0x1010531 + field public static final int contextUri = 16844077; // 0x101052d field public static final int controlX1 = 16843772; // 0x10103fc field public static final int controlX2 = 16843774; // 0x10103fe field public static final int controlY1 = 16843773; // 0x10103fd @@ -1041,7 +1041,7 @@ package android { field public static final int rotation = 16843558; // 0x1010326 field public static final int rotationX = 16843559; // 0x1010327 field public static final int rotationY = 16843560; // 0x1010328 - field public static final int roundIcon = 16844080; // 0x1010530 + field public static final int roundIcon = 16844076; // 0x101052c field public static final int rowCount = 16843637; // 0x1010375 field public static final int rowDelay = 16843216; // 0x10101d0 field public static final int rowEdgeFlags = 16843329; // 0x1010241 @@ -1109,20 +1109,16 @@ package android { field public static final int shareInterpolator = 16843195; // 0x10101bb field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserLabel = 16843361; // 0x1010261 - field public static final int shortcutCategories = 16844077; // 0x101052d - field public static final int shortcutDisabledMessage = 16844076; // 0x101052c - field public static final int shortcutIcon = 16844073; // 0x1010529 + field public static final int shortcutDisabledMessage = 16844075; // 0x101052b field public static final int shortcutId = 16844072; // 0x1010528 - field public static final int shortcutIntentAction = 16844078; // 0x101052e - field public static final int shortcutIntentData = 16844079; // 0x101052f - field public static final int shortcutLongLabel = 16844075; // 0x101052b - field public static final int shortcutShortLabel = 16844074; // 0x101052a + field public static final int shortcutLongLabel = 16844074; // 0x101052a + field public static final int shortcutShortLabel = 16844073; // 0x1010529 field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 field public static final int showForAllUsers = 16844015; // 0x10104ef - field public static final int showMetadataInPreview = 16844083; // 0x1010533 + field public static final int showMetadataInPreview = 16844079; // 0x101052f field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9 field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad @@ -10112,14 +10108,14 @@ package android.content.pm { method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName); method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set); - method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); - method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setRank(int); - method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.CharSequence); } public class ShortcutManager { diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 896fa43cd11b1..064b9090c4448 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -30,6 +30,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; @@ -245,7 +246,7 @@ public final class ShortcutInfo implements Parcelable { mTextResId = b.mTextResId; mDisabledMessage = b.mDisabledMessage; mDisabledMessageResId = b.mDisabledMessageResId; - mCategories = clone(b.mCategories); + mCategories = cloneCategories(b.mCategories); mIntent = b.mIntent; if (mIntent != null) { final Bundle intentExtras = mIntent.getExtras(); @@ -259,8 +260,17 @@ public final class ShortcutInfo implements Parcelable { updateTimestamp(); } - private ArraySet clone(Set source) { - return (source == null) ? null : new ArraySet<>(source); + private ArraySet cloneCategories(Set source) { + if (source == null) { + return null; + } + final ArraySet ret = new ArraySet<>(source.size()); + for (CharSequence s : source) { + if (!TextUtils.isEmpty(s)) { + ret.add(s.toString().intern()); + } + } + return ret; } /** @@ -304,7 +314,7 @@ public final class ShortcutInfo implements Parcelable { mTextResId = source.mTextResId; mDisabledMessage = source.mDisabledMessage; mDisabledMessageResId = source.mDisabledMessageResId; - mCategories = clone(source.mCategories); + mCategories = cloneCategories(source.mCategories); if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { mIntent = source.mIntent; mIntentPersistableExtras = source.mIntentPersistableExtras; @@ -614,7 +624,7 @@ public final class ShortcutInfo implements Parcelable { mDisabledMessageResName = null; } if (source.mCategories != null) { - mCategories = clone(source.mCategories); + mCategories = cloneCategories(source.mCategories); } if (source.mIntent != null) { mIntent = source.mIntent; @@ -752,7 +762,7 @@ public final class ShortcutInfo implements Parcelable { * an icon. The recommend max length is 10 characters. */ @NonNull - public Builder setShortLabel(@NonNull String shortLabel) { + public Builder setShortLabel(@NonNull CharSequence shortLabel) { Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set"); mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel"); return this; @@ -776,14 +786,14 @@ public final class ShortcutInfo implements Parcelable { * The recommend max length is 25 characters. */ @NonNull - public Builder setLongLabel(@NonNull String longLabel) { + public Builder setLongLabel(@NonNull CharSequence longLabel) { Preconditions.checkState(mTextResId == 0, "longLabelResId already set"); mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel"); return this; } /** @hide -- old signature, the internal code still uses it. */ - public Builder setTitle(@NonNull String value) { + public Builder setTitle(@NonNull CharSequence value) { return setShortLabel(value); } @@ -793,7 +803,7 @@ public final class ShortcutInfo implements Parcelable { } /** @hide -- old signature, the internal code still uses it. */ - public Builder setText(@NonNull String value) { + public Builder setText(@NonNull CharSequence value) { return setLongLabel(value); } @@ -813,7 +823,7 @@ public final class ShortcutInfo implements Parcelable { } @NonNull - public Builder setDisabledMessage(@NonNull String disabledMessage) { + public Builder setDisabledMessage(@NonNull CharSequence disabledMessage) { Preconditions.checkState( mDisabledMessageResId == 0, "disabledMessageResId already set"); mDisabledMessage = @@ -1355,6 +1365,37 @@ public final class ShortcutInfo implements Parcelable { mIconResName = iconResName; } + /** + * Replaces the intent + * + * @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}. + * + * @hide + */ + public void setIntent(Intent intent) throws IllegalArgumentException { + Preconditions.checkNotNull(intent); + + final Bundle intentExtras = intent.getExtras(); + + mIntent = intent; + + if (intentExtras != null) { + intent.replaceExtras((Bundle) null); + mIntentPersistableExtras = new PersistableBundle(intentExtras); + } else { + mIntentPersistableExtras = null; + } + } + + /** + * Replaces the categories. + * + * @hide + */ + public void setCategories(Set categories) { + mCategories = cloneCategories(categories); + } + private ShortcutInfo(Parcel source) { final ClassLoader cl = getClass().getClassLoader(); @@ -1591,7 +1632,7 @@ public final class ShortcutInfo implements Parcelable { mDisabledMessage = disabledMessage; mDisabledMessageResId = disabledMessageResId; mDisabledMessageResName = disabledMessageResName; - mCategories = clone(categories); + mCategories = cloneCategories(categories); mIntent = intent; mIntentPersistableExtras = intentPersistableExtras; mRank = rank; diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index d0c6a8ef012e6..13e1d00458b2d 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8251,12 +8251,13 @@ i - + - - - + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index da31767b79726..c187d2ca7d880 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2733,13 +2733,9 @@ =============================================================== --> - - - - diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index ace14acda16df..d637586c8def7 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -682,6 +682,8 @@ class ShortcutPackage extends ShortcutPackageItem { changed |= pushOutExcessShortcuts(); } + s.verifyStates(); + if (changed) { // This will send a notification to the launcher, and also save . s.packageShortcutsChanged(getPackageName(), getPackageUserId()); @@ -774,6 +776,7 @@ class ShortcutPackage extends ShortcutPackageItem { } removeOrphans(); } + adjustRanks(); return changed; } @@ -810,7 +813,6 @@ class ShortcutPackage extends ShortcutPackageItem { deleteDynamicWithId(shortcut.getId()); } } - service.verifyStates(); return changed; } diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index ec19927ad75a8..c349b758b0cee 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -24,10 +24,10 @@ import android.content.pm.PackageInfo; import android.content.pm.ShortcutInfo; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.net.Uri; import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; +import android.util.Log; import android.util.Slog; import android.util.Xml; @@ -48,10 +48,12 @@ public class ShortcutParser { private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE @VisibleForTesting - static final String METADATA_KEY = "android.pm.Shortcuts"; + static final String METADATA_KEY = "android.app.shortcuts"; private static final String TAG_SHORTCUTS = "shortcuts"; private static final String TAG_SHORTCUT = "shortcut"; + private static final String TAG_INTENT = "intent"; + private static final String TAG_CATEGORIES = "categories"; @Nullable public static List parseShortcuts(ShortcutService service, @@ -60,10 +62,17 @@ public class ShortcutParser { List result = null; - if (pi != null && pi.activities != null) { - for (ActivityInfo activityInfo : pi.activities) { - result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result); + try { + if (pi != null && pi.activities != null) { + for (ActivityInfo activityInfo : pi.activities) { + result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result); + } } + } catch (RuntimeException e) { + // Resource ID mismatch may cause various runtime exceptions when parsing XMLs. + service.wtf( + "Exception caught while parsing shortcut XML for package=" + packageName, e); + return null; } return result; } @@ -89,14 +98,58 @@ public class ShortcutParser { final int maxShortcuts = service.getMaxActivityShortcuts(); int numShortcuts = 0; + // We instantiate ShortcutInfo at , but we add it to the list at , + // after parsing . We keep the current one in here. + ShortcutInfo currentShortcut = null; + + Set categories = null; + outer: while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) { + final int depth = parser.getDepth(); + final String tag = parser.getName(); + + // When a shortcut tag is closing, publish. + if ((type == XmlPullParser.END_TAG) && (depth == 2) && (TAG_SHORTCUT.equals(tag))) { + if (currentShortcut == null) { + // Shortcut was invalid. + continue; + } + final ShortcutInfo si = currentShortcut; + currentShortcut = null; // Make sure to null out for the next iteration. + + if (si.getIntent() == null) { + Log.e(TAG, "Shortcut " + si.getId() + " has no intent. Skipping it."); + continue; + } + + if (numShortcuts >= maxShortcuts) { + Log.e(TAG, "More than " + maxShortcuts + " shortcuts found for " + + activityInfo.getComponentName() + ". Skipping the rest."); + return result; + } + if (categories != null) { + si.setCategories(categories); + categories = null; + } + + if (result == null) { + result = new ArrayList<>(); + } + result.add(si); + numShortcuts++; + rank++; + if (ShortcutService.DEBUG) { + Slog.d(TAG, "Shortcut added: " + si.toInsecureString()); + } + continue; + } + + // Otherwise, just look at start tags. if (type != XmlPullParser.START_TAG) { continue; } - final int depth = parser.getDepth(); - final String tag = parser.getName(); if (depth == 1 && TAG_SHORTCUTS.equals(tag)) { continue; // Root tag. @@ -104,35 +157,73 @@ public class ShortcutParser { if (depth == 2 && TAG_SHORTCUT.equals(tag)) { final ShortcutInfo si = parseShortcutAttributes( service, attrs, packageName, activity, userId, rank); + if (si == null) { + // Shortcut was invalid. + continue; + } if (ShortcutService.DEBUG) { - Slog.d(TAG, "Shortcut=" + si); + Slog.d(TAG, "Shortcut found: " + si.toInsecureString()); } if (result != null) { for (int i = result.size() - 1; i >= 0; i--) { if (si.getId().equals(result.get(i).getId())) { - Slog.w(TAG, "Duplicate shortcut ID detected, skipping."); + Log.e(TAG, "Duplicate shortcut ID detected. Skipping it."); continue outer; } } } + if (!si.isEnabled()) { + // Just set the default intent to disabled shortcuts. + si.setIntent(new Intent(Intent.ACTION_VIEW)); + } + currentShortcut = si; + categories = null; + continue; + } + if (depth == 3 && TAG_INTENT.equals(tag)) { + if ((currentShortcut == null) + || (currentShortcut.getIntentNoExtras() != null) + || !currentShortcut.isEnabled()) { + Log.e(TAG, "Ignoring excessive intent tag."); + continue; + } - if (si != null) { - if (numShortcuts >= maxShortcuts) { - Slog.w(TAG, "More than " + maxShortcuts + " shortcuts found for " - + activityInfo.getComponentName() + ", ignoring the rest."); - return result; - } - - if (result == null) { - result = new ArrayList<>(); - } - result.add(si); - numShortcuts++; - rank++; + final Intent intent = Intent.parseIntent(service.mContext.getResources(), + parser, attrs); + if (TextUtils.isEmpty(intent.getAction())) { + Log.e(TAG, "Shortcut intent action must be provided. activity=" + activity); + continue; + } + try { + currentShortcut.setIntent(intent); + } catch (RuntimeException e) { + // This shouldn't happen because intents in XML can't have complicated + // extras, but just in case Intent.parseIntent() supports such a thing one + // day. + Log.e(TAG, "Shortcut's extras contain un-persistable values. Skipping it."); + continue; } continue; } - Slog.w(TAG, "Unknown tag " + tag); + if (depth == 3 && TAG_CATEGORIES.equals(tag)) { + if ((currentShortcut == null) + || (currentShortcut.getCategories() != null)) { + continue; + } + final String name = parseCategories(service, attrs); + if (TextUtils.isEmpty(name)) { + Log.e(TAG, "Empty category found. activity=" + activity); + continue; + } + + if (categories == null) { + categories = new ArraySet<>(); + } + categories.add(name); + continue; + } + + Log.w(TAG, "Unknown tag " + tag + " at depth " + depth); } } finally { if (parser != null) { @@ -142,6 +233,16 @@ public class ShortcutParser { return result; } + private static String parseCategories(ShortcutService service, AttributeSet attrs) { + final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs, + R.styleable.ShortcutCategories); + try { + return sa.getString(R.styleable.ShortcutCategories_name); + } finally { + sa.recycle(); + } + } + private static ShortcutInfo parseShortcutAttributes(ShortcutService service, AttributeSet attrs, String packageName, ComponentName activity, @UserIdInt int userId, int rank) { @@ -150,14 +251,11 @@ public class ShortcutParser { try { final String id = sa.getString(R.styleable.Shortcut_shortcutId); final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true); - final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0); + final int iconResId = sa.getResourceId(R.styleable.Shortcut_icon, 0); final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0); final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0); final int disabledMessageResId = sa.getResourceId( R.styleable.Shortcut_shortcutDisabledMessage, 0); - final String categories = sa.getString(R.styleable.Shortcut_shortcutCategories); - String intentAction = sa.getString(R.styleable.Shortcut_shortcutIntentAction); - final String intentData = sa.getString(R.styleable.Shortcut_shortcutIntentData); if (TextUtils.isEmpty(id)) { Slog.w(TAG, "Shortcut ID must be provided. activity=" + activity); @@ -167,31 +265,6 @@ public class ShortcutParser { Slog.w(TAG, "Shortcut title must be provided. activity=" + activity); return null; } - if (TextUtils.isEmpty(intentAction)) { - if (enabled) { - Slog.w(TAG, "Shortcut intent action must be provided. activity=" + activity); - return null; - } else { - // Disabled shortcut doesn't have to have an action, but just set VIEW as the - // default. - intentAction = Intent.ACTION_VIEW; - } - } - - final ArraySet categoriesSet; - if (categories == null) { - categoriesSet = null; - } else { - final String[] arr = categories.split(":"); - categoriesSet = new ArraySet<>(arr.length); - for (String v : arr) { - categoriesSet.add(v); - } - } - final Intent intent = new Intent(intentAction); - if (!TextUtils.isEmpty(intentData)) { - intent.setData(Uri.parse(intentData)); - } return createShortcutFromManifest( service, @@ -202,8 +275,6 @@ public class ShortcutParser { titleResId, textResId, disabledMessageResId, - categoriesSet, - intent, rank, iconResId, enabled); @@ -214,8 +285,8 @@ public class ShortcutParser { private static ShortcutInfo createShortcutFromManifest(ShortcutService service, @UserIdInt int userId, String id, String packageName, ComponentName activityComponent, - int titleResId, int textResId, int disabledMessageResId, Set categories, - Intent intent, int rank, int iconResId, boolean enabled) { + int titleResId, int textResId, int disabledMessageResId, + int rank, int iconResId, boolean enabled) { final int flags = (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED) @@ -239,8 +310,8 @@ public class ShortcutParser { null, // disabled message string disabledMessageResId, null, // disabled message res name - categories, - intent, + null, // categories + null, // intent null, // intent extras rank, null, // extras diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 16191687770c4..be8eeed728259 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1510,6 +1510,10 @@ public class ShortcutService extends IShortcutService.Stub { ps.clearAllImplicitRanks(); assignImplicitRanks(newShortcuts); + for (int i = 0; i < size; i++) { + fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); + } + // First, remove all un-pinned; dynamic shortcuts ps.deleteAllDynamicShortcuts(); diff --git a/services/tests/servicestests/res/drawable/icon3.png b/services/tests/servicestests/res/drawable/icon3.png new file mode 100644 index 0000000000000000000000000000000000000000..64eb294363d27527d9a303d3b1a3733bd18ad775 GIT binary patch literal 5810 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 literal 0 HcmV?d00001 diff --git a/services/tests/servicestests/res/xml/shortcut_1.xml b/services/tests/servicestests/res/xml/shortcut_1.xml index f6d54fce84482..e3f9172cc23a6 100644 --- a/services/tests/servicestests/res/xml/shortcut_1.xml +++ b/services/tests/servicestests/res/xml/shortcut_1.xml @@ -2,12 +2,17 @@ + > + + + + + diff --git a/services/tests/servicestests/res/xml/shortcut_1_alt.xml b/services/tests/servicestests/res/xml/shortcut_1_alt.xml index bf14f49cae9c4..2d5e8e7cbd77c 100644 --- a/services/tests/servicestests/res/xml/shortcut_1_alt.xml +++ b/services/tests/servicestests/res/xml/shortcut_1_alt.xml @@ -17,12 +17,17 @@ + > + + + + + diff --git a/services/tests/servicestests/res/xml/shortcut_1_disable.xml b/services/tests/servicestests/res/xml/shortcut_1_disable.xml index 81a84b40a8bbe..e3ee3a0786e52 100644 --- a/services/tests/servicestests/res/xml/shortcut_1_disable.xml +++ b/services/tests/servicestests/res/xml/shortcut_1_disable.xml @@ -17,7 +17,7 @@ + > + + + + + + > + + + + diff --git a/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml index 2f814b78cc313..b00ec60e0ff57 100644 --- a/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml +++ b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml @@ -17,11 +17,19 @@ + > + + + + > + + + diff --git a/services/tests/servicestests/res/xml/shortcut_5.xml b/services/tests/servicestests/res/xml/shortcut_5.xml index 56dba0ef52970..9551100cefe1f 100644 --- a/services/tests/servicestests/res/xml/shortcut_5.xml +++ b/services/tests/servicestests/res/xml/shortcut_5.xml @@ -17,37 +17,69 @@ + > + + + + + + > + + + + + > + + + + android:shortcutShortLabel="@string/shortcut_title2" + > + + + + + + + > + + + + + + + diff --git a/services/tests/servicestests/res/xml/shortcut_5_alt.xml b/services/tests/servicestests/res/xml/shortcut_5_alt.xml index 74085d9ee1e18..f79cd6f15737a 100644 --- a/services/tests/servicestests/res/xml/shortcut_5_alt.xml +++ b/services/tests/servicestests/res/xml/shortcut_5_alt.xml @@ -17,37 +17,58 @@ + > + + + + + + > + + + + + > + + + + > + + + + > + + + diff --git a/services/tests/servicestests/res/xml/shortcut_5_reverse.xml b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml index 3d6eb222ce5b4..d5b7c8fde0bfe 100644 --- a/services/tests/servicestests/res/xml/shortcut_5_reverse.xml +++ b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml @@ -17,37 +17,62 @@ + > + + + + + + > + + + + + > + + + + > + + + + > + + + + + + + diff --git a/services/tests/servicestests/res/xml/shortcut_error_1.xml b/services/tests/servicestests/res/xml/shortcut_error_1.xml index 5822496fac113..3990d0270506f 100644 --- a/services/tests/servicestests/res/xml/shortcut_error_1.xml +++ b/services/tests/servicestests/res/xml/shortcut_error_1.xml @@ -16,11 +16,19 @@ + > + + + + > + + + diff --git a/services/tests/servicestests/res/xml/shortcut_error_2.xml b/services/tests/servicestests/res/xml/shortcut_error_2.xml index ca67ec70af2fe..a6f715025cc67 100644 --- a/services/tests/servicestests/res/xml/shortcut_error_2.xml +++ b/services/tests/servicestests/res/xml/shortcut_error_2.xml @@ -16,11 +16,19 @@ + > + + + + > + + + diff --git a/services/tests/servicestests/res/xml/shortcut_error_3.xml b/services/tests/servicestests/res/xml/shortcut_error_3.xml index fb7b31cf2e1c1..a7b9b84a9e3cc 100644 --- a/services/tests/servicestests/res/xml/shortcut_error_3.xml +++ b/services/tests/servicestests/res/xml/shortcut_error_3.xml @@ -21,6 +21,10 @@ + > + + + diff --git a/services/tests/servicestests/res/xml/shortcut_error_4.xml b/services/tests/servicestests/res/xml/shortcut_error_4.xml new file mode 100644 index 0000000000000..3697bb4144d6e --- /dev/null +++ b/services/tests/servicestests/res/xml/shortcut_error_4.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 de06047cf14e0..56232c03ea4d1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -4884,6 +4884,117 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } + public void testManifestShortcuts_intentDefinitions() { + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_error_4); + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + // Make sure invalid ones are not published. + // Note that at this point disabled ones don't show up because they weren't pinned. + assertWith(getCallerShortcuts()) + .haveIds("ms1", "ms2") + .areAllManifest() + .areAllNotDynamic() + .areAllNotPinned() + .areAllImmutable() + .areAllEnabled() + .forShortcutWithId("ms1", si -> { + assertTrue(si.isEnabled()); + assertEquals("action1", si.getIntent().getAction()); + }) + .forShortcutWithId("ms2", si -> { + assertTrue(si.isEnabled()); + assertEquals("action2_1", si.getIntent().getAction()); + }); + }); + + // Publish 5 enabled to pin some, so we can later test disabled manfiest shortcuts.. + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_5); + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + // Make sure 5 manifest shortcuts are published. + assertWith(getCallerShortcuts()) + .haveIds("ms1", "ms2", "ms3", "ms4", "ms5") + .areAllManifest() + .areAllNotDynamic() + .areAllNotPinned() + .areAllImmutable() + .areAllEnabled(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("ms3", "ms4", "ms5"), HANDLE_USER_0); + }); + + // Make sure they're pinned. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1", "ms2", "ms3", "ms4", "ms5") + .selectByIds("ms1", "ms2") + .areAllNotPinned() + .areAllEnabled() + + .revertToOriginalList() + .selectByIds("ms3", "ms4", "ms5") + .areAllPinned() + .areAllEnabled(); + }); + + // Update the app. + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_error_4); + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + + // Make sure 3, 4 and 5 still exist but disabled. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1", "ms2", "ms3", "ms4", "ms5") + .areAllNotDynamic() + .areAllImmutable() + + .selectByIds("ms1", "ms2") + .areAllManifest() + .areAllNotPinned() + .areAllEnabled() + + .revertToOriginalList() + .selectByIds("ms3", "ms4", "ms5") + .areAllNotManifest() + .areAllPinned() + .areAllDisabled() + + .revertToOriginalList() + .forShortcutWithId("ms1", si -> { + assertEquals(si.getId(), "action1", si.getIntent().getAction()); + }) + .forShortcutWithId("ms2", si -> { + assertEquals(si.getId(), "action2_1", si.getIntent().getAction()); + }) + .forShortcutWithId("ms3", si -> { + assertEquals(si.getId(), Intent.ACTION_VIEW, si.getIntent().getAction()); + }) + .forShortcutWithId("ms4", si -> { + assertEquals(si.getId(), Intent.ACTION_VIEW, si.getIntent().getAction()); + }) + .forShortcutWithId("ms5", si -> { + assertEquals(si.getId(), "action", si.getIntent().getAction()); + }); + }); + } + public void testManifestShortcuts_checkAllFields() { mService.handleUnlockUser(USER_0); @@ -4897,63 +5008,95 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // Only the valid one is published. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled( - mManager.getManifestShortcuts()))), - "ms1", "ms2", "ms3", "ms4", "ms5"); + assertWith(getCallerShortcuts()) + .haveIds("ms1", "ms2", "ms3", "ms4", "ms5") + .areAllManifest() + .areAllImmutable() + .areAllEnabled() + .areAllNotPinned() + .areAllNotDynamic() - // check first shortcut. - ShortcutInfo si = getCallerShortcut("ms1"); + .forShortcutWithId("ms1", si -> { + assertEquals(R.drawable.icon1, si.getIconResourceId()); + assertEquals(new ComponentName(CALLING_PACKAGE_1, + ShortcutActivity.class.getName()), + si.getActivity()); - assertEquals("ms1", si.getId()); - assertEquals(R.drawable.icon1, si.getIconResourceId()); - assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), - si.getActivity()); + assertEquals(R.string.shortcut_title1, si.getTitleResId()); + assertEquals("r" + R.string.shortcut_title1, si.getTitleResName()); + assertEquals(R.string.shortcut_text1, si.getTextResId()); + assertEquals("r" + R.string.shortcut_text1, si.getTextResName()); + assertEquals(R.string.shortcut_disabled_message1, + si.getDisabledMessageResourceId()); + assertEquals("r" + R.string.shortcut_disabled_message1, + si.getDisabledMessageResName()); - assertEquals(R.string.shortcut_title1, si.getTitleResId()); - assertEquals("r" + R.string.shortcut_title1, si.getTitleResName()); - assertEquals(R.string.shortcut_text1, si.getTextResId()); - assertEquals("r" + R.string.shortcut_text1, si.getTextResName()); - assertEquals(R.string.shortcut_disabled_message1, si.getDisabledMessageResourceId()); - assertEquals("r" + R.string.shortcut_disabled_message1, si.getDisabledMessageResName()); + assertEquals(set("android.shortcut.conversation", "android.shortcut.media"), + si.getCategories()); + assertEquals("action1", si.getIntent().getAction()); + assertEquals(Uri.parse("http://a.b.c/1"), si.getIntent().getData()); + }) - assertEquals(set("android.shortcut.conversation", "android.shortcut.media"), - si.getCategories()); - assertEquals("action1", si.getIntent().getAction()); - assertEquals(Uri.parse("http://a.b.c/1"), si.getIntent().getData()); + .forShortcutWithId("ms2", si -> { + assertEquals("ms2", si.getId()); + assertEquals(R.drawable.icon2, si.getIconResourceId()); - // check another - si = getCallerShortcut("ms2"); + assertEquals(R.string.shortcut_title2, si.getTitleResId()); + assertEquals("r" + R.string.shortcut_title2, si.getTitleResName()); + assertEquals(R.string.shortcut_text2, si.getTextResId()); + assertEquals("r" + R.string.shortcut_text2, si.getTextResName()); + assertEquals(R.string.shortcut_disabled_message2, + si.getDisabledMessageResourceId()); + assertEquals("r" + R.string.shortcut_disabled_message2, + si.getDisabledMessageResName()); - assertEquals("ms2", si.getId()); - assertEquals(R.drawable.icon2, si.getIconResourceId()); + assertEquals(set("android.shortcut.conversation"), si.getCategories()); + assertEquals("action2", si.getIntent().getAction()); + assertEquals(null, si.getIntent().getData()); + }) - assertEquals(R.string.shortcut_title2, si.getTitleResId()); - assertEquals("r" + R.string.shortcut_title2, si.getTitleResName()); - assertEquals(R.string.shortcut_text2, si.getTextResId()); - assertEquals("r" + R.string.shortcut_text2, si.getTextResName()); - assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResourceId()); - assertEquals("r" + R.string.shortcut_disabled_message2, si.getDisabledMessageResName()); + .forShortcutWithId("ms3", si -> { + assertEquals(0, si.getIconResourceId()); + assertEquals(R.string.shortcut_title1, si.getTitleResId()); + assertEquals("r" + R.string.shortcut_title1, si.getTitleResName()); - assertEquals(set("android.shortcut.conversation"), si.getCategories()); - assertEquals("action2", si.getIntent().getAction()); - assertEquals(null, si.getIntent().getData()); + assertEquals(0, si.getTextResId()); + assertEquals(null, si.getTextResName()); + assertEquals(0, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); - // check another - si = getCallerShortcut("ms3"); + assertEmpty(si.getCategories()); + assertEquals("android.intent.action.VIEW", si.getIntent().getAction()); + assertEquals(null, si.getIntent().getData()); + }) - assertEquals("ms3", si.getId()); - assertEquals(0, si.getIconResourceId()); - assertEquals(R.string.shortcut_title1, si.getTitleResId()); - assertEquals("r" + R.string.shortcut_title1, si.getTitleResName()); + .forShortcutWithId("ms4", si -> { + assertEquals(0, si.getIconResourceId()); + assertEquals(R.string.shortcut_title2, si.getTitleResId()); + assertEquals("r" + R.string.shortcut_title2, si.getTitleResName()); - assertEquals(0, si.getTextResId()); - assertEquals(null, si.getTextResName()); - assertEquals(0, si.getDisabledMessageResourceId()); - assertEquals(null, si.getDisabledMessageResName()); + assertEquals(0, si.getTextResId()); + assertEquals(null, si.getTextResName()); + assertEquals(0, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); - assertEquals(null, si.getCategories()); - assertEquals("android.intent.action.VIEW", si.getIntent().getAction()); - assertEquals(null, si.getIntent().getData()); + assertEquals(set("cat"), si.getCategories()); + assertEquals("android.intent.action.VIEW2", si.getIntent().getAction()); + assertEquals(null, si.getIntent().getData()); + }) + + .forShortcutWithId("ms5", si -> { + si = getCallerShortcut("ms5"); + assertEquals("action", si.getIntent().getAction()); + assertEquals("http://www/", si.getIntent().getData().toString()); + assertEquals("foo/bar", si.getIntent().getType()); + assertEquals( + new ComponentName("abc", ".xyz"), si.getIntent().getComponent()); + + assertEquals(set("cat1", "cat2"), si.getIntent().getCategories()); + assertEquals("value1", si.getIntent().getStringExtra("key1")); + assertEquals("value2", si.getIntent().getStringExtra("key2")); + }); }); } diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 7ba4c6830a5c3..712bc1e8c3011 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -43,9 +43,11 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.test.MoreAsserts; +import android.util.ArraySet; import android.util.Log; import junit.framework.Assert; +import junit.framework.AssertionFailedError; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; @@ -66,6 +68,7 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -259,9 +262,12 @@ public class ShortcutManagerTestUtils { } } - public static List assertEmpty(List list) { - assertEquals(0, list.size()); - return list; + public static > T assertEmpty(T collection) { + if (collection == null) { + return collection; // okay. + } + assertEquals(0, collection.size()); + return collection; } public static List filter(List list, Predicate p) { @@ -653,42 +659,73 @@ public class ShortcutManagerTestUtils { * New style assertion that allows chained calls. */ public static class ShortcutListAsserter { + private final ShortcutListAsserter mOriginal; private final List mList; ShortcutListAsserter(List list) { + this(null, list); + } + + private ShortcutListAsserter(ShortcutListAsserter original, List list) { + mOriginal = original == null ? this : original; mList = new ArrayList<>(list); } + public ShortcutListAsserter revertToOriginalList() { + return mOriginal; + } + public ShortcutListAsserter selectDynamic() { - return new ShortcutListAsserter( + return new ShortcutListAsserter(this, filter(mList, ShortcutInfo::isDynamic)); } public ShortcutListAsserter selectManifest() { - return new ShortcutListAsserter( + return new ShortcutListAsserter(this, filter(mList, ShortcutInfo::isManifestShortcut)); } public ShortcutListAsserter selectPinned() { - return new ShortcutListAsserter( + return new ShortcutListAsserter(this, filter(mList, ShortcutInfo::isPinned)); } public ShortcutListAsserter selectByActivity(ComponentName activity) { - return new ShortcutListAsserter( + return new ShortcutListAsserter(this, ShortcutManagerTestUtils.filterByActivity(mList, activity)); } public ShortcutListAsserter selectByChangedSince(long time) { - return new ShortcutListAsserter( + return new ShortcutListAsserter(this, ShortcutManagerTestUtils.changedSince(mList, time)); } + public ShortcutListAsserter selectByIds(String... ids) { + final Set idSet = set(ids); + final ArrayList selected = new ArrayList<>(); + for (ShortcutInfo si : mList) { + if (idSet.contains(si.getId())) { + selected.add(si); + idSet.remove(si.getId()); + } + } + if (idSet.size() > 0) { + fail("Shortcuts not found for IDs=" + idSet); + } + + return new ShortcutListAsserter(this, selected); + } + public ShortcutListAsserter toSortByRank() { - return new ShortcutListAsserter( + return new ShortcutListAsserter(this, ShortcutManagerTestUtils.sortedByRank(mList)); } + public ShortcutListAsserter call(Consumer> c) { + c.accept(mList); + return this; + } + public ShortcutListAsserter haveIds(String... expectedIds) { assertShortcutIds(mList, expectedIds); return this; @@ -701,7 +738,8 @@ public class ShortcutManagerTestUtils { private ShortcutListAsserter haveSequentialRanks() { for (int i = 0; i < mList.size(); i++) { - assertEquals("Rank not sequential", i, mList.get(i).getRank()); + final ShortcutInfo si = mList.get(i); + assertEquals("Rank not sequential: id=" + si.getId(), i, si.getRank()); } return this; } @@ -717,5 +755,87 @@ public class ShortcutManagerTestUtils { assertEquals(0, mList.size()); return this; } + + public ShortcutListAsserter areAllDynamic() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isDynamic())); + return this; + } + + public ShortcutListAsserter areAllNotDynamic() { + forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isDynamic())); + return this; + } + + public ShortcutListAsserter areAllPinned() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isPinned())); + return this; + } + + public ShortcutListAsserter areAllNotPinned() { + forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isPinned())); + return this; + } + + public ShortcutListAsserter areAllManifest() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isManifestShortcut())); + return this; + } + + public ShortcutListAsserter areAllNotManifest() { + forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isManifestShortcut())); + return this; + } + + public ShortcutListAsserter areAllImmutable() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isImmutable())); + return this; + } + + public ShortcutListAsserter areAllMutable() { + forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isImmutable())); + return this; + } + + public ShortcutListAsserter areAllEnabled() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isEnabled())); + return this; + } + + public ShortcutListAsserter areAllDisabled() { + forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isEnabled())); + return this; + } + + public ShortcutListAsserter forAllShortcuts(Consumer sa) { + for (int i = 0; i < mList.size(); i++) { + final ShortcutInfo si = mList.get(i); + sa.accept(si); + } + return this; + } + + public ShortcutListAsserter forShortcut(Predicate p, + Consumer sa) { + boolean found = false; + for (int i = 0; i < mList.size(); i++) { + final ShortcutInfo si = mList.get(i); + if (p.test(si)) { + found = true; + try { + sa.accept(si); + } catch (Throwable e) { + throw new AssertionError("Assertion failed for shortcut " + si.getId(), e); + } + } + } + assertTrue("Shortcut with the given condition not found.", found); + return this; + } + + public ShortcutListAsserter forShortcutWithId(String id, Consumer sa) { + forShortcut(si -> si.getId().equals(id), sa); + + return this; + } } }