Update manifest shortcut XML schema

Use the standard <intent> 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
diff --git a/api/current.txt b/api/current.txt
index ae6a8bc..57f1f65 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -420,9 +420,9 @@
     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 @@
     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 @@
     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 @@
     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<java.lang.String>);
-    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 15e8673..0f37751 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -527,9 +527,9 @@
     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 @@
     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 @@
     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 @@
     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<java.lang.String>);
-    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 4bb7a79..ec10ccc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -420,9 +420,9 @@
     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 @@
     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 @@
     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 @@
     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<java.lang.String>);
-    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 896fa43..064b909 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -30,6 +30,7 @@
 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 @@
         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 @@
         updateTimestamp();
     }
 
-    private <T> ArraySet<T> clone(Set<T> source) {
-        return (source == null) ? null : new ArraySet<>(source);
+    private ArraySet<String> cloneCategories(Set<String> source) {
+        if (source == null) {
+            return null;
+        }
+        final ArraySet<String> ret = new ArraySet<>(source.size());
+        for (CharSequence s : source) {
+            if (!TextUtils.isEmpty(s)) {
+                ret.add(s.toString().intern());
+            }
+        }
+        return ret;
     }
 
     /**
@@ -304,7 +314,7 @@
             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 @@
             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 @@
          * 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 @@
          * 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 @@
         }
 
         /** @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 @@
         }
 
         @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 @@
         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<String> categories) {
+        mCategories = cloneCategories(categories);
+    }
+
     private ShortcutInfo(Parcel source) {
         final ClassLoader cl = getClass().getClassLoader();
 
@@ -1591,7 +1632,7 @@
         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 d0c6a8e..13e1d00 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8251,12 +8251,13 @@
     <declare-styleable name="Shortcut">
         <attr name="shortcutId" format="string" />
         <attr name="enabled" />
-        <attr name="shortcutIcon" format="reference" />
+        <attr name="icon" />
         <attr name="shortcutShortLabel" format="reference" />
         <attr name="shortcutLongLabel" format="reference" />
         <attr name="shortcutDisabledMessage" format="reference" />
-        <attr name="shortcutCategories" format="string" />
-        <attr name="shortcutIntentAction" format="string" />
-        <attr name="shortcutIntentData" format="string" />
+    </declare-styleable>
+
+    <declare-styleable name="ShortcutCategories">
+        <attr name="name" />
     </declare-styleable>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index da31767..c187d2c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2733,13 +2733,9 @@
        =============================================================== -->
     <eat-comment />
     <public type="attr" name="shortcutId" />
-    <public type="attr" name="shortcutIcon" />
     <public type="attr" name="shortcutShortLabel" />
     <public type="attr" name="shortcutLongLabel" />
     <public type="attr" name="shortcutDisabledMessage" />
-    <public type="attr" name="shortcutCategories" />
-    <public type="attr" name="shortcutIntentAction" />
-    <public type="attr" name="shortcutIntentData" />
     <public type="attr" name="roundIcon" />
 
     <public type="attr" name="contextUri" />
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index ace14ac..d637586 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 @@
             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 @@
             }
             removeOrphans();
         }
+        adjustRanks();
         return changed;
     }
 
@@ -810,7 +813,6 @@
                 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 ec19927..c349b75 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.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 @@
     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<ShortcutInfo> parseShortcuts(ShortcutService service,
@@ -60,10 +62,17 @@
 
         List<ShortcutInfo> 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 @@
             final int maxShortcuts = service.getMaxActivityShortcuts();
             int numShortcuts = 0;
 
+            // We instantiate ShortcutInfo at <shortcut>, but we add it to the list at </shortcut>,
+            // after parsing <intent>.  We keep the current one in here.
+            ShortcutInfo currentShortcut = null;
+
+            Set<String> 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 @@
                 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 @@
         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 @@
         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 @@
                 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<String> 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 @@
                     titleResId,
                     textResId,
                     disabledMessageResId,
-                    categoriesSet,
-                    intent,
                     rank,
                     iconResId,
                     enabled);
@@ -214,8 +285,8 @@
 
     private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
             @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
-            int titleResId, int textResId, int disabledMessageResId, Set<String> 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 @@
                 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 16191687..be8eeed 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 @@
             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 0000000..64eb294
--- /dev/null
+++ b/services/tests/servicestests/res/drawable/icon3.png
Binary files differ
diff --git a/services/tests/servicestests/res/xml/shortcut_1.xml b/services/tests/servicestests/res/xml/shortcut_1.xml
index f6d54fc..e3f9172 100644
--- a/services/tests/servicestests/res/xml/shortcut_1.xml
+++ b/services/tests/servicestests/res/xml/shortcut_1.xml
@@ -2,12 +2,17 @@
     <shortcut
         android:shortcutId="ms1"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon1"
+        android:icon="@drawable/icon1"
         android:shortcutShortLabel="@string/shortcut_title1"
         android:shortcutLongLabel="@string/shortcut_text1"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
-        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
-        android:shortcutIntentAction="action1"
-        android:shortcutIntentData="data1"
-    />
+        >
+        <intent
+            android:action="action1"
+            android:data="data1"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1_alt.xml b/services/tests/servicestests/res/xml/shortcut_1_alt.xml
index bf14f49..2d5e8e7 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 @@
     <shortcut
         android:shortcutId="ms1-alt"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon1"
+        android:icon="@drawable/icon1"
         android:shortcutShortLabel="@string/shortcut_title1"
         android:shortcutLongLabel="@string/shortcut_text1"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
-        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
-        android:shortcutIntentAction="action1"
-        android:shortcutIntentData="data1"
-    />
+        >
+        <intent
+            android:action="action1"
+            android:data="data1"
+        >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1_disable.xml b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
index 81a84b4..e3ee3a0 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 @@
     <shortcut
         android:shortcutId="ms1"
         android:enabled="false"
-        android:shortcutIcon="@drawable/icon2"
+        android:icon="@drawable/icon2"
         android:shortcutShortLabel="@string/shortcut_title2"
         android:shortcutLongLabel="@string/shortcut_text2"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
diff --git a/services/tests/servicestests/res/xml/shortcut_2.xml b/services/tests/servicestests/res/xml/shortcut_2.xml
index 96ed382..f7ea803 100644
--- a/services/tests/servicestests/res/xml/shortcut_2.xml
+++ b/services/tests/servicestests/res/xml/shortcut_2.xml
@@ -17,23 +17,32 @@
     <shortcut
         android:shortcutId="ms1"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon1"
+        android:icon="@drawable/icon1"
         android:shortcutShortLabel="@string/shortcut_title1"
         android:shortcutLongLabel="@string/shortcut_text1"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
-        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
-        android:shortcutIntentAction="action1"
-        android:shortcutIntentData="http://a.b.c/"
-    />
+        >
+        <intent
+            android:action="action1"
+            android:data="http://a.b.c/"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms2"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon2"
+        android:icon="@drawable/icon2"
         android:shortcutShortLabel="@string/shortcut_title2"
         android:shortcutLongLabel="@string/shortcut_text2"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
-        android:shortcutCategories="android.shortcut.conversation"
-        android:shortcutIntentAction="action2"
-        android:shortcutIntentData="http://a.b.c/2"
-    />
+        >
+        <intent
+            android:action="action2"
+            android:data="http://a.b.c/2"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
index 2f814b7..b00ec60 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 @@
     <shortcut
         android:shortcutId="ms1"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="action1"
-    />
+        >
+        <intent
+            android:action="action1"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="ms1"
         android:shortcutShortLabel="@string/shortcut_title2"
-        android:shortcutIntentAction="action2"
-    />
+        >
+        <intent
+            android:action="action2"
+            >
+        </intent>
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5.xml b/services/tests/servicestests/res/xml/shortcut_5.xml
index 56dba0e..9551100 100644
--- a/services/tests/servicestests/res/xml/shortcut_5.xml
+++ b/services/tests/servicestests/res/xml/shortcut_5.xml
@@ -17,37 +17,69 @@
     <shortcut
         android:shortcutId="ms1"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon1"
+        android:icon="@drawable/icon1"
         android:shortcutShortLabel="@string/shortcut_title1"
         android:shortcutLongLabel="@string/shortcut_text1"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
-        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
-        android:shortcutIntentAction="action1"
-        android:shortcutIntentData="http://a.b.c/1"
-    />
+        >
+        <intent
+            android:action="action1"
+            android:data="http://a.b.c/1"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms2"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon2"
+        android:icon="@drawable/icon2"
         android:shortcutShortLabel="@string/shortcut_title2"
         android:shortcutLongLabel="@string/shortcut_text2"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
-        android:shortcutCategories="android.shortcut.conversation"
-        android:shortcutIntentAction="action2"
-    />
+        >
+        <intent
+            android:action="action2"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms3"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="ms4"
-        android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        android:shortcutShortLabel="@string/shortcut_title2"
+        >
+        <intent
+            android:action="android.intent.action.VIEW2"
+            >
+        </intent>
+        <categories />
+        <categories android:name="" />
+        <categories android:name="cat" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms5"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="action"
+            android:data="http://www/"
+            android:targetPackage="abc"
+            android:targetClass=".xyz"
+            android:mimeType="foo/bar"
+            >
+            <categories android:name="cat1" />
+            <categories android:name="cat2" />
+            <extra android:name="key1" android:value="value1" />
+            <extra android:name="key2" android:value="value2" />
+        </intent>
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_alt.xml b/services/tests/servicestests/res/xml/shortcut_5_alt.xml
index 74085d9..f79cd6f 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 @@
     <shortcut
         android:shortcutId="ms1_alt"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon1"
+        android:icon="@drawable/icon1"
         android:shortcutShortLabel="@string/shortcut_title1"
         android:shortcutLongLabel="@string/shortcut_text1"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
-        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
-        android:shortcutIntentAction="action1"
-        android:shortcutIntentData="http://a.b.c/1"
-    />
+        >
+        <intent
+            android:action="action1"
+            android:data="http://a.b.c/1"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms2_alt"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon2"
+        android:icon="@drawable/icon2"
         android:shortcutShortLabel="@string/shortcut_title2"
         android:shortcutLongLabel="@string/shortcut_text2"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
-        android:shortcutCategories="android.shortcut.conversation"
-        android:shortcutIntentAction="action2"
-    />
+        >
+        <intent
+            android:action="action2"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms3_alt"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="ms4_alt"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="ms5_alt"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_reverse.xml b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
index 3d6eb22..d5b7c8f 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 @@
     <shortcut
         android:shortcutId="ms5"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon1"
+        android:icon="@drawable/icon1"
         android:shortcutShortLabel="@string/shortcut_title1"
         android:shortcutLongLabel="@string/shortcut_text1"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
-        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
-        android:shortcutIntentAction="action1"
-        android:shortcutIntentData="http://a.b.c/1"
-    />
+        >
+        <intent
+            android:action="action1"
+            android:data="http://a.b.c/1"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms4"
         android:enabled="true"
-        android:shortcutIcon="@drawable/icon2"
+        android:icon="@drawable/icon2"
         android:shortcutShortLabel="@string/shortcut_title2"
         android:shortcutLongLabel="@string/shortcut_text2"
         android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
-        android:shortcutCategories="android.shortcut.conversation"
-        android:shortcutIntentAction="action2"
-    />
+        >
+        <intent
+            android:action="action2"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+    </shortcut>
     <shortcut
         android:shortcutId="ms3"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="ms2"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="ms1"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="action"
+            >
+            <categories android:name="cat1" />
+            <categories android:name="cat2" />
+            <extra android:name="key1" android:value="value1" />
+            <extra android:name="key2" android:value="value2" />
+        </intent>
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_1.xml b/services/tests/servicestests/res/xml/shortcut_error_1.xml
index 5822496..3990d02 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 @@
 <shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
     <shortcut
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="x1"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_2.xml b/services/tests/servicestests/res/xml/shortcut_error_2.xml
index ca67ec7..a6f7150 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 @@
 <shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
     <shortcut
         android:shortcutId="manifest-shortcut-3"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
     <shortcut
         android:shortcutId="x2"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
 </shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_3.xml b/services/tests/servicestests/res/xml/shortcut_error_3.xml
index fb7b31c..a7b9b84 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 @@
     <shortcut
         android:shortcutId="x3"
         android:shortcutShortLabel="@string/shortcut_title1"
-        android:shortcutIntentAction="android.intent.action.VIEW"
-    />
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
 </shortcuts>
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 0000000..3697bb4
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_4.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- This is valid -->
+    <shortcut
+        android:shortcutId="ms1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        >
+        <intent android:action="action1" />
+    </shortcut>
+
+    <!-- Invalid: no intent -->
+    <shortcut
+        android:shortcutId="ms_ignored1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        />
+
+    <!-- Valid: more than one intent; first one will be picked. -->
+    <shortcut
+        android:shortcutId="ms2"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        >
+        <intent android:action="action2_1" />
+        <intent android:action="action2_2" />
+    </shortcut>
+
+    <!-- Valid: disabled shortcut doesn't need an intent -->
+    <shortcut
+        android:shortcutId="ms3"
+        android:enabled="false"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        />
+
+    <!-- Valid, but disabled shortcut's intent will be ignored. -->
+    <shortcut
+        android:shortcutId="ms4"
+        android:enabled="false"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        >
+        <intent android:action="action4" />
+    </shortcut>
+
+    <!-- Invalid, no intent action -->
+    <shortcut
+        android:shortcutId="ms_ignored2"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        >
+        <intent android:data="x"/>
+    </shortcut>
+</shortcuts>
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 de06047..56232c0 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 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 @@
 
         // 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 7ba4c68..712bc1e 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.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.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 static <T> List<T> assertEmpty(List<T> list) {
-        assertEquals(0, list.size());
-        return list;
+    public static <T extends Collection<?>> T assertEmpty(T collection) {
+        if (collection == null) {
+            return collection; // okay.
+        }
+        assertEquals(0, collection.size());
+        return collection;
     }
 
     public static List<ShortcutInfo> filter(List<ShortcutInfo> list, Predicate<ShortcutInfo> p) {
@@ -653,42 +659,73 @@
      * New style assertion that allows chained calls.
      */
     public static class ShortcutListAsserter {
+        private final ShortcutListAsserter mOriginal;
         private final List<ShortcutInfo> mList;
 
         ShortcutListAsserter(List<ShortcutInfo> list) {
+            this(null, list);
+        }
+
+        private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> 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<String> idSet = set(ids);
+            final ArrayList<ShortcutInfo> 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<List<ShortcutInfo>> c) {
+            c.accept(mList);
+            return this;
+        }
+
         public ShortcutListAsserter haveIds(String... expectedIds) {
             assertShortcutIds(mList, expectedIds);
             return this;
@@ -701,7 +738,8 @@
 
         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 @@
             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<ShortcutInfo> sa) {
+            for (int i = 0; i < mList.size(); i++) {
+                final ShortcutInfo si = mList.get(i);
+                sa.accept(si);
+            }
+            return this;
+        }
+
+        public ShortcutListAsserter forShortcut(Predicate<ShortcutInfo> p,
+                Consumer<ShortcutInfo> 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<ShortcutInfo> sa) {
+            forShortcut(si -> si.getId().equals(id), sa);
+
+            return this;
+        }
     }
 }