ShortcutManager: Support manifest shortcuts

- Now apps can publish shortcuts via AndroidManifest.xml
(Sample: I5b127150)

- Rename some APIs per API council feedback

- Publishers can now "disable" shortcuts.
(https://docs.google.com/presentation/d/1raUn1QBURDb1yrd6mSmVxII9ezNh3MWukODdtufJ29U/edit#slide=id.g13ef592464_7_39)

Bug 28785283
Bug 28536066

Change-Id: I4a126841e43e40139bb4baa6d0f98ad7b3a75ac1
diff --git a/api/current.txt b/api/current.txt
index 427d9b3..114ef0d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1106,6 +1106,15 @@
     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 = 16844078; // 0x101052e
+    field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
+    field public static final int shortcutIcon = 16844074; // 0x101052a
+    field public static final int shortcutId = 16844072; // 0x1010528
+    field public static final int shortcutIntentAction = 16844079; // 0x101052f
+    field public static final int shortcutIntentData = 16844080; // 0x1010530
+    field public static final int shortcutRank = 16844073; // 0x1010529
+    field public static final int shortcutText = 16844076; // 0x101052c
+    field public static final int shortcutTitle = 16844075; // 0x101052b
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
@@ -9534,13 +9543,14 @@
 
   public static class LauncherApps.ShortcutQuery {
     ctor public LauncherApps.ShortcutQuery();
-    method public void setActivity(android.content.ComponentName);
-    method public void setChangedSince(long);
-    method public void setPackage(java.lang.String);
-    method public void setQueryFlags(int);
-    method public void setShortcutIds(java.util.List<java.lang.String>);
+    method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
+    method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
+    method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
+    method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
+    method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
     field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
     field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
+    field public static final int FLAG_GET_MANIFEST = 8; // 0x8
     field public static final int FLAG_GET_PINNED = 2; // 0x2
   }
 
@@ -10044,29 +10054,30 @@
 
   public final class ShortcutInfo implements android.os.Parcelable {
     method public int describeContents();
-    method public android.content.ComponentName getActivityComponent();
+    method public android.content.ComponentName getActivity();
     method public java.util.Set<java.lang.String> getCategories();
-    method public java.lang.String getDisabledMessage();
+    method public java.lang.CharSequence getDisabledMessage();
     method public int getDisabledMessageResId();
     method public android.os.PersistableBundle getExtras();
     method public int getIconResourceId();
     method public java.lang.String getId();
     method public android.content.Intent getIntent();
     method public long getLastChangedTimestamp();
-    method public java.lang.String getPackageName();
+    method public java.lang.String getPackage();
     method public int getRank();
-    method public java.lang.String getText();
+    method public java.lang.CharSequence getText();
     method public int getTextResId();
-    method public java.lang.String getTitle();
+    method public java.lang.CharSequence getTitle();
     method public int getTitleResId();
     method public android.os.UserHandle getUserHandle();
     method public boolean hasIconFile();
     method public boolean hasIconResource();
     method public boolean hasKeyFieldsOnly();
     method public boolean hasStringResourcesResolved();
-    method public boolean isDisabled();
     method public boolean isDynamic();
-    method public boolean isFromManifest();
+    method public boolean isEnabled();
+    method public boolean isImmutable();
+    method public boolean isManifestShortcut();
     method public boolean isPinned();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10075,10 +10086,11 @@
     field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
     field public static final int FLAG_DISABLED = 64; // 0x40
     field public static final int FLAG_DYNAMIC = 1; // 0x1
-    field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
     field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
     field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+    field public static final int FLAG_IMMUTABLE = 256; // 0x100
     field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
+    field public static final int FLAG_MANIFEST = 32; // 0x20
     field public static final int FLAG_PINNED = 2; // 0x2
     field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
     field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10087,19 +10099,16 @@
   public static class ShortcutInfo.Builder {
     ctor public ShortcutInfo.Builder(android.content.Context);
     method public android.content.pm.ShortcutInfo build();
-    method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+    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 setDisabledMessageResId(int);
     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 setRank(int);
     method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
-    method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
     method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
-    method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
   }
 
   public class ShortcutManager {
@@ -10107,9 +10116,12 @@
     method public void disableShortcuts(java.util.List<java.lang.String>);
     method public void disableShortcuts(java.util.List<java.lang.String>, int);
     method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+    method public void enableShortcuts(java.util.List<java.lang.String>);
     method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
-    method public int getIconMaxDimensions();
-    method public int getMaxDynamicShortcutCount();
+    method public int getIconMaxHeight();
+    method public int getIconMaxWidth();
+    method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
+    method public int getMaxShortcutCountForActivity();
     method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
     method public long getRateLimitResetTime();
     method public int getRemainingCallCount();
diff --git a/api/system-current.txt b/api/system-current.txt
index 3e8d6fa..505052d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1216,6 +1216,15 @@
     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 = 16844078; // 0x101052e
+    field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
+    field public static final int shortcutIcon = 16844074; // 0x101052a
+    field public static final int shortcutId = 16844072; // 0x1010528
+    field public static final int shortcutIntentAction = 16844079; // 0x101052f
+    field public static final int shortcutIntentData = 16844080; // 0x1010530
+    field public static final int shortcutRank = 16844073; // 0x1010529
+    field public static final int shortcutText = 16844076; // 0x101052c
+    field public static final int shortcutTitle = 16844075; // 0x101052b
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
@@ -9887,13 +9896,14 @@
 
   public static class LauncherApps.ShortcutQuery {
     ctor public LauncherApps.ShortcutQuery();
-    method public void setActivity(android.content.ComponentName);
-    method public void setChangedSince(long);
-    method public void setPackage(java.lang.String);
-    method public void setQueryFlags(int);
-    method public void setShortcutIds(java.util.List<java.lang.String>);
+    method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
+    method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
+    method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
+    method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
+    method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
     field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
     field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
+    field public static final int FLAG_GET_MANIFEST = 8; // 0x8
     field public static final int FLAG_GET_PINNED = 2; // 0x2
   }
 
@@ -10467,29 +10477,30 @@
 
   public final class ShortcutInfo implements android.os.Parcelable {
     method public int describeContents();
-    method public android.content.ComponentName getActivityComponent();
+    method public android.content.ComponentName getActivity();
     method public java.util.Set<java.lang.String> getCategories();
-    method public java.lang.String getDisabledMessage();
+    method public java.lang.CharSequence getDisabledMessage();
     method public int getDisabledMessageResId();
     method public android.os.PersistableBundle getExtras();
     method public int getIconResourceId();
     method public java.lang.String getId();
     method public android.content.Intent getIntent();
     method public long getLastChangedTimestamp();
-    method public java.lang.String getPackageName();
+    method public java.lang.String getPackage();
     method public int getRank();
-    method public java.lang.String getText();
+    method public java.lang.CharSequence getText();
     method public int getTextResId();
-    method public java.lang.String getTitle();
+    method public java.lang.CharSequence getTitle();
     method public int getTitleResId();
     method public android.os.UserHandle getUserHandle();
     method public boolean hasIconFile();
     method public boolean hasIconResource();
     method public boolean hasKeyFieldsOnly();
     method public boolean hasStringResourcesResolved();
-    method public boolean isDisabled();
     method public boolean isDynamic();
-    method public boolean isFromManifest();
+    method public boolean isEnabled();
+    method public boolean isImmutable();
+    method public boolean isManifestShortcut();
     method public boolean isPinned();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10498,10 +10509,11 @@
     field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
     field public static final int FLAG_DISABLED = 64; // 0x40
     field public static final int FLAG_DYNAMIC = 1; // 0x1
-    field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
     field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
     field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+    field public static final int FLAG_IMMUTABLE = 256; // 0x100
     field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
+    field public static final int FLAG_MANIFEST = 32; // 0x20
     field public static final int FLAG_PINNED = 2; // 0x2
     field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
     field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10510,19 +10522,16 @@
   public static class ShortcutInfo.Builder {
     ctor public ShortcutInfo.Builder(android.content.Context);
     method public android.content.pm.ShortcutInfo build();
-    method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+    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 setDisabledMessageResId(int);
     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 setRank(int);
     method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
-    method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
     method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
-    method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
   }
 
   public class ShortcutManager {
@@ -10530,9 +10539,12 @@
     method public void disableShortcuts(java.util.List<java.lang.String>);
     method public void disableShortcuts(java.util.List<java.lang.String>, int);
     method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+    method public void enableShortcuts(java.util.List<java.lang.String>);
     method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
-    method public int getIconMaxDimensions();
-    method public int getMaxDynamicShortcutCount();
+    method public int getIconMaxHeight();
+    method public int getIconMaxWidth();
+    method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
+    method public int getMaxShortcutCountForActivity();
     method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
     method public long getRateLimitResetTime();
     method public int getRemainingCallCount();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1448c6c..a7d4f9a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1106,6 +1106,15 @@
     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 = 16844078; // 0x101052e
+    field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
+    field public static final int shortcutIcon = 16844074; // 0x101052a
+    field public static final int shortcutId = 16844072; // 0x1010528
+    field public static final int shortcutIntentAction = 16844079; // 0x101052f
+    field public static final int shortcutIntentData = 16844080; // 0x1010530
+    field public static final int shortcutRank = 16844073; // 0x1010529
+    field public static final int shortcutText = 16844076; // 0x101052c
+    field public static final int shortcutTitle = 16844075; // 0x101052b
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
@@ -9546,13 +9555,14 @@
 
   public static class LauncherApps.ShortcutQuery {
     ctor public LauncherApps.ShortcutQuery();
-    method public void setActivity(android.content.ComponentName);
-    method public void setChangedSince(long);
-    method public void setPackage(java.lang.String);
-    method public void setQueryFlags(int);
-    method public void setShortcutIds(java.util.List<java.lang.String>);
+    method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
+    method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
+    method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
+    method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
+    method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
     field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
     field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
+    field public static final int FLAG_GET_MANIFEST = 8; // 0x8
     field public static final int FLAG_GET_PINNED = 2; // 0x2
   }
 
@@ -10057,29 +10067,30 @@
 
   public final class ShortcutInfo implements android.os.Parcelable {
     method public int describeContents();
-    method public android.content.ComponentName getActivityComponent();
+    method public android.content.ComponentName getActivity();
     method public java.util.Set<java.lang.String> getCategories();
-    method public java.lang.String getDisabledMessage();
+    method public java.lang.CharSequence getDisabledMessage();
     method public int getDisabledMessageResId();
     method public android.os.PersistableBundle getExtras();
     method public int getIconResourceId();
     method public java.lang.String getId();
     method public android.content.Intent getIntent();
     method public long getLastChangedTimestamp();
-    method public java.lang.String getPackageName();
+    method public java.lang.String getPackage();
     method public int getRank();
-    method public java.lang.String getText();
+    method public java.lang.CharSequence getText();
     method public int getTextResId();
-    method public java.lang.String getTitle();
+    method public java.lang.CharSequence getTitle();
     method public int getTitleResId();
     method public android.os.UserHandle getUserHandle();
     method public boolean hasIconFile();
     method public boolean hasIconResource();
     method public boolean hasKeyFieldsOnly();
     method public boolean hasStringResourcesResolved();
-    method public boolean isDisabled();
     method public boolean isDynamic();
-    method public boolean isFromManifest();
+    method public boolean isEnabled();
+    method public boolean isImmutable();
+    method public boolean isManifestShortcut();
     method public boolean isPinned();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10088,10 +10099,11 @@
     field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
     field public static final int FLAG_DISABLED = 64; // 0x40
     field public static final int FLAG_DYNAMIC = 1; // 0x1
-    field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
     field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
     field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+    field public static final int FLAG_IMMUTABLE = 256; // 0x100
     field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
+    field public static final int FLAG_MANIFEST = 32; // 0x20
     field public static final int FLAG_PINNED = 2; // 0x2
     field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
     field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10100,19 +10112,16 @@
   public static class ShortcutInfo.Builder {
     ctor public ShortcutInfo.Builder(android.content.Context);
     method public android.content.pm.ShortcutInfo build();
-    method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+    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 setDisabledMessageResId(int);
     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 setRank(int);
     method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
-    method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
     method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
-    method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
   }
 
   public class ShortcutManager {
@@ -10121,9 +10130,12 @@
     method public void disableShortcuts(java.util.List<java.lang.String>);
     method public void disableShortcuts(java.util.List<java.lang.String>, int);
     method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+    method public void enableShortcuts(java.util.List<java.lang.String>);
     method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
-    method public int getIconMaxDimensions();
-    method public int getMaxDynamicShortcutCount();
+    method public int getIconMaxHeight();
+    method public int getIconMaxWidth();
+    method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
+    method public int getMaxShortcutCountForActivity();
     method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
     method public long getRateLimitResetTime();
     method public int getRemainingCallCount();
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 06200bf..1c373f9 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -28,6 +28,8 @@
 
     ParceledListSlice getDynamicShortcuts(String packageName, int userId);
 
+    ParceledListSlice getManifestShortcuts(String packageName, int userId);
+
     boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
             int userId);
 
@@ -42,6 +44,8 @@
     void disableShortcuts(String packageName, in List shortcutIds, String disabledMessage,
             int disabledMessageResId, int userId);
 
+    void enableShortcuts(String packageName, in List shortcutIds, int userId);
+
     int getMaxDynamicShortcutCount(String packageName, int userId);
 
     int getRemainingCallCount(String packageName, int userId);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index acd85cb..7ee7048 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -188,6 +188,11 @@
         public static final int FLAG_GET_PINNED = 1 << 1;
 
         /**
+         * Include manifest shortcuts in the result.
+         */
+        public static final int FLAG_GET_MANIFEST = 1 << 3;
+
+        /**
          * Requests "key" fields only.  See {@link ShortcutInfo#hasKeyFieldsOnly()} for which
          * fields are available.
          */
@@ -198,6 +203,7 @@
                 value = {
                         FLAG_GET_DYNAMIC,
                         FLAG_GET_PINNED,
+                        FLAG_GET_MANIFEST,
                         FLAG_GET_KEY_FIELDS_ONLY,
                 })
         @Retention(RetentionPolicy.SOURCE)
@@ -224,39 +230,44 @@
          * If non-zero, returns only shortcuts that have been added or updated since the timestamp,
          * which is a milliseconds since the Epoch.
          */
-        public void setChangedSince(long changedSince) {
+        public ShortcutQuery setChangedSince(long changedSince) {
             mChangedSince = changedSince;
+            return this;
         }
 
         /**
          * If non-null, returns only shortcuts from the package.
          */
-        public void setPackage(@Nullable String packageName) {
+        public ShortcutQuery setPackage(@Nullable String packageName) {
             mPackage = packageName;
+            return this;
         }
 
         /**
          * If non-null, return only the specified shortcuts by ID.  When setting this field,
          * a packange name must also be set with {@link #setPackage}.
          */
-        public void setShortcutIds(@Nullable List<String> shortcutIds) {
+        public ShortcutQuery setShortcutIds(@Nullable List<String> shortcutIds) {
             mShortcutIds = shortcutIds;
+            return this;
         }
 
         /**
-         * If non-null, returns only shortcuts associated with the activity, which are
-         * {@link ShortcutInfo}s that have null {@link ShortcutInfo#getActivityComponent()}, or
-         * {@link ShortcutInfo#getActivityComponent()} equals to {@code activity}.
+         * If non-null, returns only shortcuts associated with the activity; i.e.
+         * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
+         * to {@code activity}.
          */
-        public void setActivity(@Nullable ComponentName activity) {
+        public ShortcutQuery setActivity(@Nullable ComponentName activity) {
             mActivity = activity;
+            return this;
         }
 
         /**
          * Set query options.
          */
-        public void setQueryFlags(@QueryFlags int queryFlags) {
+        public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
             mQueryFlags = queryFlags;
+            return this;
         }
     }
 
@@ -525,7 +536,7 @@
      */
     public ParcelFileDescriptor getShortcutIconFd(
             @NonNull ShortcutInfo shortcut) {
-        return getShortcutIconFd(shortcut.getPackageName(), shortcut.getId(),
+        return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
                 shortcut.getUserId());
     }
 
@@ -555,14 +566,14 @@
         }
     }
 
-    /** TODO Javadoc */
+    /** TODO Javadoc  (not implemented yet) */
     public Drawable getShortcutIconDrawable(int density) {
-        throw new RuntimeException("TODO implement it");
+        throw new RuntimeException("TODO not implemented yet");
     }
 
-    /** TODO Javadoc */
+    /** TODO Javadoc  (not implemented yet) */
     public Drawable getShortcutBadgedIconDrawable(int density) {
-        throw new RuntimeException("TODO implement it");
+        throw new RuntimeException("TODO not implemented yet");
     }
 
     /**
@@ -600,7 +611,7 @@
      */
     public boolean startShortcut(@NonNull ShortcutInfo shortcut,
             @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
-        return startShortcut(shortcut.getPackageName(), shortcut.getId(),
+        return startShortcut(shortcut.getPackage(), shortcut.getId(),
                 sourceBounds, startActivityOptions,
                 shortcut.getUserId());
     }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 3648d9e..c83aa22 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -70,7 +70,7 @@
     public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
 
     /* @hide */
-    public static final int FLAG_FROM_MANIFEST = 1 << 5;
+    public static final int FLAG_MANIFEST = 1 << 5;
 
     /* @hide */
     public static final int FLAG_DISABLED = 1 << 6;
@@ -78,6 +78,9 @@
     /* @hide */
     public static final int FLAG_STRINGS_RESOLVED = 1 << 7;
 
+    /* @hide */
+    public static final int FLAG_IMMUTABLE = 1 << 8;
+
     /** @hide */
     @IntDef(flag = true,
             value = {
@@ -86,9 +89,10 @@
             FLAG_HAS_ICON_RES,
             FLAG_HAS_ICON_FILE,
             FLAG_KEY_FIELDS_ONLY,
-            FLAG_FROM_MANIFEST,
+            FLAG_MANIFEST,
             FLAG_DISABLED,
             FLAG_STRINGS_RESOLVED,
+            FLAG_IMMUTABLE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ShortcutFlags {}
@@ -133,7 +137,7 @@
     private final String mPackageName;
 
     @Nullable
-    private ComponentName mActivityComponent;
+    private ComponentName mActivity;
 
     @Nullable
     private Icon mIcon;
@@ -141,17 +145,17 @@
     private int mTitleResId;
 
     @Nullable
-    private String mTitle;
+    private CharSequence mTitle;
 
     private int mTextResId;
 
     @Nullable
-    private String mText;
+    private CharSequence mText;
 
     private int mDisabledMessageResId;
 
     @Nullable
-    private String mDisabledMessage;
+    private CharSequence mDisabledMessage;
 
     @Nullable
     private ArraySet<String> mCategories;
@@ -196,7 +200,7 @@
         // Note we can't do other null checks here because SM.updateShortcuts() takes partial
         // information.
         mPackageName = b.mContext.getPackageName();
-        mActivityComponent = b.mActivityComponent;
+        mActivity = b.mActivity;
         mIcon = b.mIcon;
         mTitle = b.mTitle;
         mTitleResId = b.mTitleResId;
@@ -229,7 +233,7 @@
      */
     public void enforceMandatoryFields() {
         Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
-        Preconditions.checkNotNull(mActivityComponent, "activityComponent must be provided");
+        Preconditions.checkNotNull(mActivity, "activity must be provided");
         if (mTitle == null && mTitleResId == 0) {
             throw new IllegalArgumentException("Shortcut title must be provided");
         }
@@ -250,7 +254,7 @@
         mIconResourceId = source.mIconResourceId;
 
         if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
-            mActivityComponent = source.mActivityComponent;
+            mActivity = source.mActivity;
 
             if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
                 mIcon = source.mIcon;
@@ -309,6 +313,17 @@
     }
 
     /**
+     * @hide
+     */
+    public void ensureUpdatableWith(ShortcutInfo source) {
+        Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
+        Preconditions.checkState(mId.equals(source.mId), "ID must match");
+        Preconditions.checkState(mPackageName.equals(source.mPackageName),
+                "Package name must match");
+        Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+    }
+
+    /**
      * Copy non-null/zero fields from another {@link ShortcutInfo}.  Only "public" information
      * will be overwritten.  The timestamp will be updated.
      *
@@ -316,16 +331,15 @@
      * - mBitmapPath will not change
      * - Current time will be set to timestamp
      *
+     * @throws IllegalStateException if source is not compatible.
+     *
      * @hide
      */
     public void copyNonNullFieldsFrom(ShortcutInfo source) {
-        Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
-        Preconditions.checkState(mId.equals(source.mId), "ID must match");
-        Preconditions.checkState(mPackageName.equals(source.mPackageName),
-                "Package name must match");
+        ensureUpdatableWith(source);
 
-        if (source.mActivityComponent != null) {
-            mActivityComponent = source.mActivityComponent;
+        if (source.mActivity != null) {
+            mActivity = source.mActivity;
         }
 
         if (source.mIcon != null) {
@@ -401,21 +415,21 @@
 
         private String mId;
 
-        private ComponentName mActivityComponent;
+        private ComponentName mActivity;
 
         private Icon mIcon;
 
         private int mTitleResId;
 
-        private String mTitle;
+        private CharSequence mTitle;
 
         private int mTextResId;
 
-        private String mText;
+        private CharSequence mText;
 
         private int mDisabledMessageResId;
 
-        private String mDisabledMessage;
+        private CharSequence mDisabledMessage;
 
         private Set<String> mCategories;
 
@@ -451,8 +465,8 @@
          * a hint to the launcher app about which launcher icon to associate this shortcut with.
          */
         @NonNull
-        public Builder setActivityComponent(@NonNull ComponentName activityComponent) {
-            mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent");
+        public Builder setActivity(@NonNull ComponentName activity) {
+            mActivity = Preconditions.checkNotNull(activity, "activity");
             return this;
         }
 
@@ -476,7 +490,7 @@
             return this;
         }
 
-        /** TODO Javadoc */
+        /** @hide */
         public Builder setTitleResId(int titleResId) {
             Preconditions.checkState(mTitle == null, "title already set");
             mTitleResId = titleResId;
@@ -496,7 +510,7 @@
             return this;
         }
 
-        /** TODO Javadoc */
+        /** @hide */
         public Builder setTextResId(int textResId) {
             Preconditions.checkState(mText == null, "text already set");
             mTextResId = textResId;
@@ -516,7 +530,7 @@
             return this;
         }
 
-        /** TODO Javadoc */
+        /** @hide */
         public Builder setDisabledMessageResId(int disabledMessageResId) {
             Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set");
             mDisabledMessageResId = disabledMessageResId;
@@ -595,7 +609,7 @@
      * Return the package name of the creator application.
      */
     @NonNull
-    public String getPackageName() {
+    public String getPackage() {
         return mPackageName;
     }
 
@@ -606,11 +620,11 @@
      * <p>This has nothing to do with the activity that this shortcut will launch.  This is
      * a hint to the launcher app that on which launcher icon this shortcut should be shown.
      *
-     * @see Builder#setActivityComponent
+     * @see Builder#setActivity
      */
     @Nullable
-    public ComponentName getActivityComponent() {
-        return mActivityComponent;
+    public ComponentName getActivity() {
+        return mActivity;
     }
 
     /**
@@ -634,7 +648,7 @@
      * {@link #hasKeyFieldsOnly()} is true.
      */
     @Nullable
-    public String getTitle() {
+    public CharSequence getTitle() {
         return mTitle;
     }
 
@@ -647,7 +661,7 @@
      * Return the shortcut text.
      */
     @Nullable
-    public String getText() {
+    public CharSequence getText() {
         return mText;
     }
 
@@ -660,7 +674,7 @@
      * Return the message that should be shown when a shortcut in disabled state is launched.
      */
     @Nullable
-    public String getDisabledMessage() {
+    public CharSequence getDisabledMessage() {
         return mDisabledMessage;
     }
 
@@ -788,18 +802,67 @@
     }
 
     /**
-     * Return whether a shortcut is published via manifest or not.  If true, the shortcut is
-     * immutable.
+     * Return whether a shortcut is published via AndroidManifest.xml or not.  If {@code true},
+     * it's also {@link #isImmutable()}.
+     *
+     * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml,
+     * this will be set to {@code false}.  If the shortcut is not pinned, then it'll just disappear.
+     * However, if it's pinned, it will still be alive, and {@link #isEnabled()} will be
+     * {@code false} and {@link #isImmutable()} will be {@code true}.
+     *
+     * <p>NOTE this is whether a shortcut is published from the <b>current version's</b>
+     * AndroidManifest.xml.
      */
-    public boolean isFromManifest() {
-        return hasFlags(FLAG_FROM_MANIFEST);
+    public boolean isManifestShortcut() {
+        return hasFlags(FLAG_MANIFEST);
     }
 
-    /** Return whether a shortcut is disabled by publisher or not. */
-    public boolean isDisabled() {
+    /**
+     * @return true if pinned but neither dynamic nor manifest.
+     * @hide
+     */
+    public boolean isFloating() {
+        return isPinned() && !(isDynamic() || isManifestShortcut());
+    }
+
+    /** @hide */
+    public boolean isOriginallyFromManifest() {
+        return hasFlags(FLAG_IMMUTABLE);
+    }
+
+    /**
+     * Return if a shortcut is immutable, in which case it cannot be modified with any of
+     * {@link ShortcutManager} APIs.
+     *
+     * <p>All manifest shortcuts are immutable.  When a manifest shortcut is pinned and then
+     * disabled because the app is upgraded and its AndroidManifest.xml no longer publishes it,
+     * {@link #isManifestShortcut} returns {@code false}, but it is still immutable.
+     *
+     * <p>All shortcuts originally published via the {@link ShortcutManager} APIs
+     * are all mutable.
+     */
+    public boolean isImmutable() {
+        return hasFlags(FLAG_IMMUTABLE);
+    }
+
+    /**
+     * Returns {@code false} if a shortcut is disabled with
+     * {@link ShortcutManager#disableShortcuts}.
+     */
+    public boolean isEnabled() {
         return !hasFlags(FLAG_DISABLED);
     }
 
+    /** @hide */
+    public boolean isAlive() {
+        return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
+    }
+
+    /** @hide */
+    public boolean usesQuota() {
+        return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
+    }
+
     /**
      * Return whether a shortcut's icon is a resource in the owning package.
      *
@@ -833,7 +896,7 @@
      * following fields are available.
      * <ul>
      *     <li>{@link #getId()}
-     *     <li>{@link #getPackageName()}
+     *     <li>{@link #getPackage()}
      *     <li>{@link #getLastChangedTimestamp()}
      *     <li>{@link #isDynamic()}
      *     <li>{@link #isPinned()}
@@ -888,19 +951,31 @@
         mBitmapPath = bitmapPath;
     }
 
+    /** @hide */
+    public void setDisabledMessageResId(int disabledMessageResId) {
+        mDisabledMessageResId = disabledMessageResId;
+        mDisabledMessage = null;
+    }
+
+    /** @hide */
+    public void setDisabledMessage(String disabledMessage) {
+        mDisabledMessage = disabledMessage;
+        mDisabledMessageResId = 0;
+    }
+
     private ShortcutInfo(Parcel source) {
         final ClassLoader cl = getClass().getClassLoader();
 
         mUserId = source.readInt();
         mId = source.readString();
         mPackageName = source.readString();
-        mActivityComponent = source.readParcelable(cl);
+        mActivity = source.readParcelable(cl);
         mIcon = source.readParcelable(cl);
-        mTitle = source.readString();
+        mTitle = source.readCharSequence();
         mTitleResId = source.readInt();
-        mText = source.readString();
+        mText = source.readCharSequence();
         mTextResId = source.readInt();
-        mDisabledMessage = source.readString();
+        mDisabledMessage = source.readCharSequence();
         mDisabledMessageResId = source.readInt();
         mIntent = source.readParcelable(cl);
         mIntentPersistableExtras = source.readParcelable(cl);
@@ -927,13 +1002,13 @@
         dest.writeInt(mUserId);
         dest.writeString(mId);
         dest.writeString(mPackageName);
-        dest.writeParcelable(mActivityComponent, flags);
+        dest.writeParcelable(mActivity, flags);
         dest.writeParcelable(mIcon, flags);
-        dest.writeString(mTitle);
+        dest.writeCharSequence(mTitle);
         dest.writeInt(mTitleResId);
-        dest.writeString(mText);
+        dest.writeCharSequence(mText);
         dest.writeInt(mTextResId);
-        dest.writeString(mDisabledMessage);
+        dest.writeCharSequence(mDisabledMessage);
         dest.writeInt(mDisabledMessageResId);
 
         dest.writeParcelable(mIntent, flags);
@@ -991,18 +1066,43 @@
         sb.append("id=");
         sb.append(secure ? "***" : mId);
 
+        sb.append(", flags=0x");
+        sb.append(Integer.toHexString(mFlags));
+        sb.append(" [");
+        if (!isEnabled()) {
+            sb.append("X");
+        }
+        if (isImmutable()) {
+            sb.append("Im");
+        }
+        if (isManifestShortcut()) {
+            sb.append("M");
+        }
+        if (isDynamic()) {
+            sb.append("D");
+        }
+        if (isPinned()) {
+            sb.append("P");
+        }
+        if (hasIconFile()) {
+            sb.append("If");
+        }
+        if (hasIconResource()) {
+            sb.append("Ir");
+        }
+        if (hasKeyFieldsOnly()) {
+            sb.append("K");
+        }
+        if (hasStringResourcesResolved()) {
+            sb.append("Sr");
+        }
+        sb.append("]");
+
         sb.append(", packageName=");
         sb.append(mPackageName);
 
-        if (isDynamic()) {
-            sb.append(", dynamic");
-        }
-        if (isPinned()) {
-            sb.append(", pinned");
-        }
-
         sb.append(", activity=");
-        sb.append(mActivityComponent);
+        sb.append(mActivity);
 
         sb.append(", title=");
         sb.append(secure ? "***" : mTitle);
@@ -1040,35 +1140,6 @@
         sb.append(", extras=");
         sb.append(mExtras);
 
-        sb.append(", flags=");
-        sb.append(mFlags);
-        sb.append(" [");
-        if (hasFlags(FLAG_DISABLED)) {
-            sb.append("X");
-        }
-        if (hasFlags(FLAG_FROM_MANIFEST)) {
-            sb.append("M");
-        }
-        if (hasFlags(FLAG_DYNAMIC)) {
-            sb.append("D");
-        }
-        if (hasFlags(FLAG_PINNED)) {
-            sb.append("P");
-        }
-        if (hasFlags(FLAG_HAS_ICON_FILE)) {
-            sb.append("If");
-        }
-        if (hasFlags(FLAG_HAS_ICON_RES)) {
-            sb.append("Ir");
-        }
-        if (hasFlags(FLAG_KEY_FIELDS_ONLY)) {
-            sb.append("K");
-        }
-        if (hasFlags(FLAG_STRINGS_RESOLVED)) {
-            sb.append("S");
-        }
-        sb.append("]");
-
         if (includeInternalData) {
 
             sb.append(", iconRes=");
@@ -1084,16 +1155,16 @@
 
     /** @hide */
     public ShortcutInfo(
-            @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
-            Icon icon, String title, int titleResId, String text, int textResId,
-            String disabledMessage, int disabledMessageResId, Set<String> categories, Intent intent,
-            PersistableBundle intentPersistableExtras,
+            @UserIdInt int userId, String id, String packageName, ComponentName activity,
+            Icon icon, CharSequence title, int titleResId, CharSequence text, int textResId,
+            CharSequence disabledMessage, int disabledMessageResId, Set<String> categories,
+            Intent intent, PersistableBundle intentPersistableExtras,
             int rank, PersistableBundle extras, long lastChangedTimestamp,
             int flags, int iconResId, String bitmapPath) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
-        mActivityComponent = activityComponent;
+        mActivity = activity;
         mIcon = icon;
         mTitle = title;
         mTitleResId = titleResId;
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 44fa98d..3d214a84 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -28,7 +28,7 @@
 
 // TODO Enhance javadoc
 /**
- * <b>TODO: Update to reflect DR changes.</b><br>
+ * <b>TODO: Update to reflect DR changes, such as manifest shortcuts.</b><br>
  *
  * {@link ShortcutManager} manages shortcuts created by applications.
  *
@@ -36,8 +36,8 @@
  *
  * An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and
  * {@link #addDynamicShortcuts(List)}.  There can be at most
- * {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same
- * application.
+ * {@link #getMaxShortcutCountForActivity()} number of dynamic shortcuts at a time from the
+ * same application.
  * A dynamic shortcut can be deleted with {@link #removeDynamicShortcuts(List)}, and apps
  * can also use {@link #removeAllDynamicShortcuts()} to delete all dynamic shortcuts.
  *
@@ -51,7 +51,8 @@
  * <p>The number of pinned shortcuts does not affect the number of dynamic shortcuts that can be
  * published by an application at a time.
  * No matter how many pinned shortcuts that Launcher has for an application, the
- * application can still always publish {@link #getMaxDynamicShortcutCount()} number of dynamic
+ * application can still always publish {@link #getMaxShortcutCountForActivity()} number of
+ * dynamic
  * shortcuts.
  *
  * <h3>Shortcut IDs</h3>
@@ -132,7 +133,7 @@
      * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
      *
      * @throws IllegalArgumentException if {@code shortcutInfoList} contains more than
-     * {@link #getMaxDynamicShortcutCount()} shortcuts.
+     * {@link #getMaxShortcutCountForActivity()} shortcuts.
      */
     public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
         try {
@@ -145,7 +146,7 @@
 
     /**
      * Return all dynamic shortcuts from the caller application.  The number of result items
-     * will not exceed the value returned by {@link #getMaxDynamicShortcutCount()}.
+     * will not exceed the value returned by {@link #getMaxShortcutCountForActivity()}.
      */
     @NonNull
     public List<ShortcutInfo> getDynamicShortcuts() {
@@ -158,6 +159,19 @@
     }
 
     /**
+     * TODO Javadoc
+     */
+    @NonNull
+    public List<ShortcutInfo> getManifestShortcuts() {
+        try {
+            return mService.getManifestShortcuts(mContext.getPackageName(), injectMyUserId())
+                    .getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Publish list of dynamic shortcuts.  If there's already dynamic or pinned shortcuts with
      * the same IDs, they will all be updated.
      *
@@ -269,9 +283,21 @@
     }
 
     /**
-     * Return the max number of dynamic shortcuts that each application can have at a time.
+     * TODO Javadoc
      */
-    public int getMaxDynamicShortcutCount() {
+    public void enableShortcuts(@NonNull List<String> shortcutIds) {
+        try {
+            mService.enableShortcuts(mContext.getPackageName(), shortcutIds, injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the max number of dynamic shortcuts + manifest shortcuts that each launcehr icon
+     * can have at a time.
+     */
+    public int getMaxShortcutCountForActivity() {
         try {
             return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId());
         } catch (RemoteException e) {
@@ -308,10 +334,23 @@
     }
 
     /**
-     * Return the max width and height for icons, in pixels.
+     * Return the max width for icons, in pixels.
      */
-    public int getIconMaxDimensions() {
+    public int getIconMaxWidth() {
         try {
+            // TODO Implement it properly using xdpi.
+            return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the max height for icons, in pixels.
+     */
+    public int getIconMaxHeight() {
+        try {
+            // TODO Implement it properly using ydpi.
             return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e0fbca1..3259907 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8237,4 +8237,17 @@
                    color. -->
         <attr name="colorBackground" />
     </declare-styleable>
+
+    <declare-styleable name="Shortcut">
+        <attr name="shortcutId" format="string" />
+        <attr name="enabled" format="boolean" />
+        <attr name="shortcutRank" format="integer" />
+        <attr name="shortcutIcon" format="reference" />
+        <attr name="shortcutTitle" format="reference" />
+        <attr name="shortcutText" format="reference" />
+        <attr name="shortcutDisabledMessage" format="reference" />
+        <attr name="shortcutCategories" format="string" />
+        <attr name="shortcutIntentAction" format="string" />
+        <attr name="shortcutIntentData" format="string" />
+    </declare-styleable>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6797799..2738dc3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2728,4 +2728,18 @@
     <public type="id" name="icon_frame" id="0x0102003e" />
     <public type="id" name="list_container" id="0x0102003f" />
     <public type="id" name="switch_widget" id="0x01020040" />
+
+  <!-- ===============================================================
+       Resources added in version N MR1 of the platform
+       =============================================================== -->
+    <eat-comment />
+    <public type="attr" name="shortcutId" />
+    <public type="attr" name="shortcutRank" />
+    <public type="attr" name="shortcutIcon" />
+    <public type="attr" name="shortcutTitle" />
+    <public type="attr" name="shortcutText" />
+    <public type="attr" name="shortcutDisabledMessage" />
+    <public type="attr" name="shortcutCategories" />
+    <public type="attr" name="shortcutIntentAction" />
+    <public type="attr" name="shortcutIntentData" />
 </resources>
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index db2b9f4..e667838 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -36,6 +36,8 @@
 
 /**
  * Launcher information used by {@link ShortcutService}.
+ *
+ * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
  */
 class ShortcutLauncher extends ShortcutPackageItem {
     private static final String TAG = ShortcutService.TAG;
@@ -125,7 +127,8 @@
                 if (si == null) {
                     continue;
                 }
-                if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
+                if (si.isDynamic() || si.isManifestShortcut()
+                        || (prevSet != null && prevSet.contains(id))) {
                     newSet.add(id);
                 }
             }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index c298683..bca3777 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,6 +20,7 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.os.PersistableBundle;
 import android.text.format.Formatter;
@@ -28,6 +29,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -44,6 +46,11 @@
 
 /**
  * Package information used by {@link ShortcutService}.
+ * User information used by {@link ShortcutService}.
+ *
+ * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
+ *
+ * TODO Max dynamic shortcuts cap should be per activity.
  */
 class ShortcutPackage extends ShortcutPackageItem {
     private static final String TAG = ShortcutService.TAG;
@@ -55,7 +62,6 @@
     private static final String TAG_CATEGORIES = "categories";
 
     private static final String ATTR_NAME = "name";
-    private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
     private static final String ATTR_CALL_COUNT = "call-count";
     private static final String ATTR_LAST_RESET = "last-reset";
     private static final String ATTR_ID = "id";
@@ -84,11 +90,6 @@
     final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
 
     /**
-     * # of dynamic shortcuts.
-     */
-    private int mDynamicShortcutCount = 0;
-
-    /**
      * # of times the package has called rate-limited APIs.
      */
     private int mApiCallCount;
@@ -129,19 +130,19 @@
      * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
      * we do some initialization for the package.
      */
-    private void onShortcutPublish() {
+    private void ensurePackageVersionInfo() {
         // Make sure we have the version code for the app.  We need the version code in
         // handlePackageUpdated().
         if (getPackageInfo().getVersionCode() < 0) {
             final ShortcutService s = mShortcutUser.mService;
 
-            final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId());
-            if (ShortcutService.DEBUG) {
-                Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
-                        versionCode));
-            }
-            if (versionCode >= 0) {
-                getPackageInfo().setVersionCode(versionCode);
+            final PackageInfo pi = s.getPackageInfo(getPackageName(), getOwnerUserId());
+            if (pi != null) {
+                if (ShortcutService.DEBUG) {
+                    Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
+                            pi.versionCode));
+                }
+                getPackageInfo().updateVersionInfo(pi);
                 s.scheduleSaveUser(getOwnerUserId());
             }
         }
@@ -168,17 +169,42 @@
         return mShortcuts.get(id);
     }
 
-    private ShortcutInfo deleteShortcut(@NonNull String id) {
+    private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) {
+        if (shortcut != null && shortcut.isImmutable()) {
+            throw new IllegalArgumentException(
+                    "Manifest shortcut ID=" + shortcut.getId()
+                            + " may not be manipulated via APIs");
+        }
+    }
+
+    private void ensureNotImmutable(@NonNull String id) {
+        ensureNotImmutable(mShortcuts.get(id));
+    }
+
+    public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) {
+        for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+            ensureNotImmutable(shortcutIds.get(i));
+        }
+    }
+
+    public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) {
+        for (int i = shortcuts.size() - 1; i >= 0; i--) {
+            ensureNotImmutable(shortcuts.get(i).getId());
+        }
+    }
+
+    private ShortcutInfo deleteShortcutInner(@NonNull String id) {
         final ShortcutInfo shortcut = mShortcuts.remove(id);
         if (shortcut != null) {
             mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
-            shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
+            shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
+                    | ShortcutInfo.FLAG_MANIFEST);
         }
         return shortcut;
     }
 
-    void addShortcut(@NonNull ShortcutInfo newShortcut) {
-        deleteShortcut(newShortcut.getId());
+    private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
+        deleteShortcutInner(newShortcut.getId());
         mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
         mShortcuts.put(newShortcut.getId(), newShortcut);
     }
@@ -188,39 +214,41 @@
      *
      * It checks the max number of dynamic shortcuts.
      */
-    public void addDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
+    public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
 
-        onShortcutPublish();
+        Preconditions.checkArgument(newShortcut.isEnabled(),
+                "add/setDynamicShortcuts() cannot publish disabled shortcuts");
+
+        ensurePackageVersionInfo();
 
         newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
 
         final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
 
         final boolean wasPinned;
-        final int newDynamicCount;
 
         if (oldShortcut == null) {
             wasPinned = false;
-            newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
         } else {
+            // It's an update case.
+            // Make sure the target is updatable. (i.e. should be mutable.)
+            oldShortcut.ensureUpdatableWith(newShortcut);
+
             wasPinned = oldShortcut.isPinned();
-            if (oldShortcut.isDynamic()) {
-                newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut.
-            } else {
-                newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
+            if (!oldShortcut.isEnabled()) {
+                newShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
             }
         }
 
-        // Make sure there's still room.
-        mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
+        // TODO Check max dynamic count.
+        // mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
 
         // Okay, make it dynamic and add.
         if (wasPinned) {
             newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
         }
 
-        addShortcut(newShortcut);
-        mDynamicShortcutCount = newDynamicCount;
+        addShortcutInner(newShortcut);
     }
 
     /**
@@ -232,7 +260,7 @@
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
 
-            if (si.isPinned() || si.isDynamic()) continue;
+            if (si.isAlive()) continue;
 
             if (removeList == null) {
                 removeList = new ArrayList<>();
@@ -241,7 +269,7 @@
         }
         if (removeList != null) {
             for (int i = removeList.size() - 1; i >= 0; i--) {
-                deleteShortcut(removeList.get(i));
+                deleteShortcutInner(removeList.get(i));
             }
         }
     }
@@ -250,29 +278,68 @@
      * Remove all dynamic shortcuts.
      */
     public void deleteAllDynamicShortcuts() {
+        boolean changed = false;
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+            if (si.isDynamic()) {
+                changed = true;
+                si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+            }
         }
-        removeOrphans();
-        mDynamicShortcutCount = 0;
+        if (changed) {
+            removeOrphans();
+        }
     }
 
     /**
      * Remove a dynamic shortcut by ID.
      */
     public void deleteDynamicWithId(@NonNull String shortcutId) {
+        deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+    }
+
+    public void disableWithId(@NonNull String shortcutId, String disabledMessage,
+            int disabledMessageResId, boolean overrideImmutable) {
+        final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
+                overrideImmutable);
+
+        if (disabled != null) {
+            if (disabledMessage != null) {
+                disabled.setDisabledMessage(disabledMessage);
+            } else if (disabledMessageResId != 0) {
+                disabled.setDisabledMessageResId(disabledMessageResId);
+            }
+        }
+    }
+
+    @Nullable
+    private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
+            boolean overrideImmutable) {
         final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
 
-        if (oldShortcut == null) {
-            return;
+        if (oldShortcut == null || !oldShortcut.isEnabled()) {
+            return null; // Doesn't exist or already disabled.
         }
-        if (oldShortcut.isDynamic()) {
-            mDynamicShortcutCount--;
+        if (!overrideImmutable) {
+            ensureNotImmutable(oldShortcut);
         }
         if (oldShortcut.isPinned()) {
-            oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+            oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
+            if (disable) {
+                oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
+            }
+            return oldShortcut;
         } else {
-            deleteShortcut(shortcutId);
+            deleteShortcutInner(shortcutId);
+            return null;
+        }
+    }
+
+    public void enableWithId(@NonNull String shortcutId) {
+        final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
+        if (shortcut != null) {
+            ensureNotImmutable(shortcut);
+            shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
         }
     }
 
@@ -430,22 +497,19 @@
         for (int i = 0; i < mShortcuts.size(); i++) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
 
-            // If it's called by non-launcher (i.e. publisher, always include -> true.
-            // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned
-            // it.
+            // Need to adjust PINNED flag depending on the caller.
+            // Basically if the caller is a launcher (callingLauncher != null) and the launcher
+            // isn't pinning it, then we need to clear PINNED for this caller.
             final boolean isPinnedByCaller = (callingLauncher == null)
                     || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
-            if (!si.isDynamic()) {
-                if (!si.isPinned()) {
-                    s.wtf("Shortcut not pinned: package " + getPackageName()
-                            + ", user=" + getPackageUserId() + ", id=" + si.getId());
-                    continue;
-                }
+
+            if (si.isFloating()) {
                 if (!isPinnedByCaller) {
                     continue;
                 }
             }
             final ShortcutInfo clone = si.clone(cloneFlag);
+
             // Fix up isPinned for the caller.  Note we need to do it before the "test" callback,
             // since it may check isPinned.
             if (!isPinnedByCaller) {
@@ -486,32 +550,81 @@
     }
 
     /**
-     * Called when the package is updated.  If there are shortcuts with resource icons, update
-     * their timestamps.
+     * Called when the package is updated or added.
+     *
+     * Add case:
+     * - Publish manifest shortcuts.
+     *
+     * Update case:
+     * - Re-publish manifest shortcuts.
+     * - If there are shortcuts with resources (icons or strings), update their timestamps.
+     *
+     * @return TRUE if any shortcuts have been changed.
      */
-    public void handlePackageUpdated(int newVersionCode) {
-        if (getPackageInfo().getVersionCode() >= newVersionCode) {
-            // Version hasn't changed; nothing to do.
-            return;
-        }
-        if (ShortcutService.DEBUG) {
-            Slog.d(TAG, String.format("Package %s updated, version %d -> %d", getPackageName(),
-                    getPackageInfo().getVersionCode(), newVersionCode));
+    public boolean handlePackageAddedOrUpdated(boolean isNewApp) {
+        final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
+                getPackageName(), getPackageUserId());
+        if (pi == null) {
+            return false; // Shouldn't happen.
         }
 
-        getPackageInfo().setVersionCode(newVersionCode);
+        if (!isNewApp) {
+            // Make sure the version code or last update time has changed.
+            // Otherwise, nothing to do.
+            if (getPackageInfo().getVersionCode() >= pi.versionCode
+                    && getPackageInfo().getLastUpdateTime() >= pi.lastUpdateTime) {
+                return false;
+            }
+        }
+
+        // Now prepare to publish manifest shortcuts.
+        List<ShortcutInfo> newManifestShortcutList = null;
+        try {
+            newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
+                    getPackageName(), getPackageUserId());
+        } catch (IOException|XmlPullParserException e) {
+            Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
+        }
+        final int manifestShortcutSize = newManifestShortcutList == null ? 0
+                : newManifestShortcutList.size();
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, String.format("Package %s has %d manifest shortcut(s)",
+                    getPackageName(), manifestShortcutSize));
+        }
+        if (isNewApp && (manifestShortcutSize == 0)) {
+            // If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
+
+            // If it's an update, then it may already have manifest shortcuts, which need to be
+            // disabled.
+            return false;
+        }
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, String.format("Package %s %s, version %d -> %d", getPackageName(),
+                    (isNewApp ? "added" : "updated"),
+                    getPackageInfo().getVersionCode(), pi.versionCode));
+        }
+
+        getPackageInfo().updateVersionInfo(pi);
 
         final ShortcutService s = mShortcutUser.mService;
 
         boolean changed = false;
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
 
-            if (si.hasAnyResources()) {
-                changed = true;
-                si.setTimestamp(s.injectCurrentTimeMillis());
+        // For existing shortcuts, update timestamps if they have any resources.
+        if (!isNewApp) {
+            for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+                final ShortcutInfo si = mShortcuts.valueAt(i);
+
+                if (si.hasAnyResources()) {
+                    changed = true;
+                    si.setTimestamp(s.injectCurrentTimeMillis());
+                }
             }
         }
+
+        // (Re-)publish manifest shortcut.
+        changed |= publishManifestShortcuts(newManifestShortcutList);
+
         if (changed) {
             // This will send a notification to the launcher, and also save .
             s.packageShortcutsChanged(getPackageName(), getPackageUserId());
@@ -519,6 +632,97 @@
             // Still save the version code.
             s.scheduleSaveUser(getPackageUserId());
         }
+        return changed;
+    }
+
+    private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) {
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, String.format(
+                    "Package %s: publishing manifest shortcuts", getPackageName()));
+        }
+        boolean changed = false;
+
+        // TODO: Check dynamic count
+
+        // TODO: Kick out dynamic if too many
+
+        // Keep the previous IDs.
+        ArraySet<String> toDisableList = null;
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+
+            if (si.isManifestShortcut()) {
+                if (toDisableList == null) {
+                    toDisableList = new ArraySet<>();
+                }
+                toDisableList.add(si.getId());
+            }
+        }
+
+        // Publish new ones.
+        if (newManifestShortcutList != null) {
+            final int newListSize = newManifestShortcutList.size();
+
+            for (int i = 0; i < newListSize; i++) {
+                changed = true;
+
+                final ShortcutInfo newShortcut = newManifestShortcutList.get(i);
+                final boolean newDisabled = !newShortcut.isEnabled();
+
+                final String id =  newShortcut.getId();
+                final ShortcutInfo oldShortcut = mShortcuts.get(id);
+
+                boolean wasPinned = false;
+
+                if (oldShortcut != null) {
+                    if (!oldShortcut.isOriginallyFromManifest()) {
+                        Slog.e(TAG, "Shortcut with ID=" + newShortcut.getId()
+                                + " exists but is not from AndroidManifest.xml, not updating.");
+                        continue;
+                    }
+                    // Take over the pinned flag.
+                    if (oldShortcut.isPinned()) {
+                        wasPinned = true;
+                        newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+                    }
+                }
+                if (newDisabled && !wasPinned) {
+                    // If the shortcut is disabled, and it was *not* pinned, then this
+                    // just doesn't have to be published.
+                    // Just keep it in toDisableList, so the previous one would be removed.
+                    continue;
+                }
+                // TODO: Check dynamic count
+
+                // Note even if enabled=false, we still need to update all fields, so do it
+                // regardless.
+                addShortcutInner(newShortcut); // This will clean up the old one too.
+
+                if (!newDisabled && toDisableList != null) {
+                    // Still alive, don't remove.
+                    toDisableList.remove(id);
+                }
+            }
+        }
+
+        // Disable the previous manifest shortcuts that are no longer in the manifest.
+        if (toDisableList != null) {
+            if (ShortcutService.DEBUG) {
+                Slog.d(TAG, String.format(
+                        "Package %s: disabling %d stale shortcuts", getPackageName(),
+                        toDisableList.size()));
+            }
+            for (int i = toDisableList.size() - 1; i >= 0; i--) {
+                changed = true;
+
+                final String id = toDisableList.valueAt(i);
+
+                disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
+                        /* overrideImmutable=*/ true);
+            }
+            removeOrphans();
+        }
+        return changed;
     }
 
     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -597,7 +801,6 @@
         out.startTag(null, TAG_ROOT);
 
         ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
-        ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
         ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
         ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
         getPackageInfo().saveToXml(out);
@@ -619,7 +822,7 @@
         out.startTag(null, TAG_SHORTCUT);
         ShortcutService.writeAttr(out, ATTR_ID, si.getId());
         // writeAttr(out, "package", si.getPackageName()); // not needed
-        ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
+        ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivity());
         // writeAttr(out, "icon", si.getIcon());  // We don't save it.
         ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
         ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
@@ -670,8 +873,6 @@
         final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
                 shortcutUser.getUserId(), packageName);
 
-        ret.mDynamicShortcutCount =
-                ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
         ret.mApiCallCount =
                 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
         ret.mLastResetTime =
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index ae9709e..7f5d931 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.pm;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.pm.PackageInfo;
 import android.util.Slog;
@@ -34,12 +35,15 @@
 
 /**
  * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
+ *
+ * All methods should be guarded by {@code ShortcutService.mLock}.
  */
 class ShortcutPackageInfo {
     private static final String TAG = ShortcutService.TAG;
 
     static final String TAG_ROOT = "package-info";
     private static final String ATTR_VERSION = "version";
+    private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
     private static final String ATTR_SHADOW = "shadow";
 
     private static final String TAG_SIGNATURE = "signature";
@@ -53,16 +57,20 @@
      */
     private boolean mIsShadow;
     private int mVersionCode = VERSION_UNKNOWN;
+    private long mLastUpdateTime;
     private ArrayList<byte[]> mSigHashes;
 
-    private ShortcutPackageInfo(int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) {
+    private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
+            ArrayList<byte[]> sigHashes, boolean isShadow) {
         mVersionCode = versionCode;
+        mLastUpdateTime = lastUpdateTime;
         mIsShadow = isShadow;
         mSigHashes = sigHashes;
     }
 
     public static ShortcutPackageInfo newEmpty() {
-        return new ShortcutPackageInfo(VERSION_UNKNOWN, new ArrayList<>(0), /* isShadow */ false);
+        return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
+                new ArrayList<>(0), /* isShadow */ false);
     }
 
     public boolean isShadow() {
@@ -77,8 +85,15 @@
         return mVersionCode;
     }
 
-    public void setVersionCode(int versionCode) {
-        mVersionCode = versionCode;
+    public long getLastUpdateTime() {
+        return mLastUpdateTime;
+    }
+
+    public void updateVersionInfo(@NonNull PackageInfo pi) {
+        if (pi != null) {
+            mVersionCode = pi.versionCode;
+            mLastUpdateTime = pi.lastUpdateTime;
+        }
     }
 
     public boolean hasSignatures() {
@@ -111,7 +126,7 @@
             Slog.e(TAG, "Can't get signatures: package=" + packageName);
             return null;
         }
-        final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode,
+        final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
                 BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
 
         return ret;
@@ -131,6 +146,7 @@
             return;
         }
         mVersionCode = pi.versionCode;
+        mLastUpdateTime = pi.lastUpdateTime;
         mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
     }
 
@@ -139,6 +155,7 @@
         out.startTag(null, TAG_ROOT);
 
         ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
+        ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
         ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
 
         for (int i = 0; i < mSigHashes.size(); i++) {
@@ -154,6 +171,9 @@
 
         final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
 
+        final long lastUpdateTime = ShortcutService.parseIntAttribute(
+                parser, ATTR_LAST_UPDATE_TIME);
+
         // When restoring from backup, it's always shadow.
         final boolean shadow =
                 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
@@ -185,6 +205,7 @@
 
         // Successfully loaded; replace the feilds.
         mVersionCode = versionCode;
+        mLastUpdateTime = lastUpdateTime;
         mIsShadow = shadow;
         mSigHashes = hashes;
     }
@@ -205,6 +226,11 @@
         pw.print(mVersionCode);
         pw.println();
 
+        pw.print(prefix);
+        pw.print("  Last package update time: ");
+        pw.print(mLastUpdateTime);
+        pw.println();
+
         for (int i = 0; i < mSigHashes.size(); i++) {
             pw.print(prefix);
             pw.print("    ");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 0c2417c..d08b974 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -26,6 +26,9 @@
 
 import java.io.IOException;
 
+/**
+ * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
+ */
 abstract class ShortcutPackageItem {
     private static final String TAG = ShortcutService.TAG;
 
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
new file mode 100644
index 0000000..44d0a9f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+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.Slog;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ShortcutParser {
+    private static final String TAG = ShortcutService.TAG;
+
+    private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE
+
+    @VisibleForTesting
+    static final String METADATA_KEY = "android.pm.Shortcuts";
+
+    private static final String TAG_SHORTCUTS = "shortcuts";
+    private static final String TAG_SHORTCUT = "shortcut";
+
+    @Nullable
+    public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
+            String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
+        final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId);
+
+        List<ShortcutInfo> result = null;
+
+        if (pi != null && pi.activities != null) {
+            for (ActivityInfo activityInfo : pi.activities) {
+                result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
+            }
+        }
+        return result;
+    }
+
+    private static List<ShortcutInfo> parseShortcutsOneFile(
+            ShortcutService service,
+            ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
+            List<ShortcutInfo> result) throws IOException, XmlPullParserException {
+        XmlResourceParser parser = null;
+        try {
+            parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
+            if (parser == null) {
+                return result;
+            }
+
+            final ComponentName activity = new ComponentName(packageName, activityInfo.name);
+
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            int type;
+
+            outer:
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) {
+                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.
+                }
+                if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
+                    final ShortcutInfo si = parseShortcutAttributes(
+                            service, attrs, packageName, activity, userId);
+                    if (ShortcutService.DEBUG) {
+                        Slog.d(TAG, "Shortcut=" + si);
+                    }
+                    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.");
+                                continue outer;
+                            }
+                        }
+                    }
+
+                    if (si != null) {
+                        if (result == null) {
+                            result = new ArrayList<>();
+                        }
+                        result.add(si);
+                    }
+                    continue;
+                }
+                Slog.w(TAG, "Unknown tag " + tag);
+            }
+        } finally {
+            if (parser != null) {
+                parser.close();
+            }
+        }
+        return result;
+    }
+
+    private static ShortcutInfo parseShortcutAttributes(ShortcutService service,
+            AttributeSet attrs, String packageName, ComponentName activity,
+            @UserIdInt int userId) {
+        final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
+                R.styleable.Shortcut);
+        try {
+            final String id = sa.getString(R.styleable.Shortcut_shortcutId);
+            final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
+            final int rank = sa.getInt(R.styleable.Shortcut_shortcutRank, 0);
+            final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0);
+            final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutTitle, 0);
+            final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutText, 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);
+                return null;
+            }
+            if (titleResId == 0) {
+                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,
+                    userId,
+                    id,
+                    packageName,
+                    activity,
+                    titleResId,
+                    textResId,
+                    disabledMessageResId,
+                    categoriesSet,
+                    intent,
+                    rank,
+                    iconResId,
+                    enabled);
+        } finally {
+            sa.recycle();
+        }
+    }
+
+    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) {
+
+        final int flags =
+                (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
+                | ShortcutInfo.FLAG_IMMUTABLE
+                | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
+
+        return new ShortcutInfo(
+                userId,
+                id,
+                packageName,
+                activityComponent,
+                null, // icon
+                null, // title string
+                titleResId,
+                null, // text string
+                textResId,
+                null, // disabled message string
+                disabledMessageResId,
+                categories,
+                intent,
+                null, // intent extras
+                rank,
+                null, // extras
+                service.injectCurrentTimeMillis(),
+                flags,
+                iconResId,
+                null); // bitmap path
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 6a10f41..dc0404c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.IShortcutService;
@@ -39,6 +40,7 @@
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.Canvas;
@@ -103,6 +105,7 @@
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
@@ -111,16 +114,21 @@
 
 /**
  * TODO:
- * - Manifest shortcuts.
+ * - Implement # of dynamic shortcuts cap.
  *
- * - Implement disableShortcuts().
+ * - HandleUnlockUser needs to be async.  Wait on it in onCleanupUser.
  *
  * - Implement reportShortcutUsed().
  *
+ * - validateForXml() should be removed.
+ *
  * - Ranks should be recalculated after each update.
  *
  * - When the system locale changes, update timestamps for shortcuts with string resources,
- *   and notify the launcher.
+ *   and notify the launcher.  Right now, it resets the throttling, but timestamps are not changed
+ *   and there's no notification either.
+ *
+ * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
  *
  * - Default launcher check does take a few ms.  Worth caching.
  *
@@ -128,11 +136,13 @@
  *   internal bitmap handling.
  *
  * - Add more call stats.
+ *
+ * - Rename getMaxDynamicShortcutCount and mMaxDynamicShortcuts
  */
 public class ShortcutService extends IShortcutService.Stub {
     static final String TAG = "ShortcutService";
 
-    static final boolean DEBUG = false; // STOPSHIP if true
+    static final boolean DEBUG = true; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
 
@@ -301,8 +311,11 @@
         int GET_APPLICATION_INFO = 3;
         int LAUNCHER_PERMISSION_CHECK = 4;
         int CLEANUP_DANGLING_BITMAPS = 5;
+        int GET_ACTIVITIES_WITH_METADATA = 6;
+        int GET_INSTALLED_APPLICATIONS = 7;
+        int CHECK_PACKAGE_CHANGES = 8;
 
-        int COUNT = CLEANUP_DANGLING_BITMAPS + 1;
+        int COUNT = CHECK_PACKAGE_CHANGES + 1;
     }
 
     final Object mStatLock = new Object();
@@ -444,6 +457,9 @@
 
     /** lifecycle event */
     void handleUnlockUser(int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "handleUnlockUser: user=" + userId);
+        }
         synchronized (mLock) {
             // Preload
             getUserShortcutsLocked(userId);
@@ -642,10 +658,10 @@
         out.endTag(null, tag);
     }
 
-    static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
+    static void writeAttr(XmlSerializer out, String name, CharSequence value) throws IOException {
         if (TextUtils.isEmpty(value)) return;
 
-        out.attribute(null, name, value);
+        out.attribute(null, name, value.toString());
     }
 
     static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
@@ -1090,7 +1106,7 @@
     FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
             throws IOException {
         final File packagePath = new File(getUserBitmapFilePath(userId),
-                shortcut.getPackageName());
+                shortcut.getPackage());
         if (!packagePath.isDirectory()) {
             packagePath.mkdirs();
             if (!packagePath.isDirectory()) {
@@ -1129,7 +1145,6 @@
             }
 
             Bitmap bitmap;
-            Bitmap bitmapToRecycle = null;
             try {
                 switch (icon.getType()) {
                     case Icon.TYPE_RESOURCE: {
@@ -1181,9 +1196,6 @@
                     }
                 }
             } finally {
-                if (bitmapToRecycle != null) {
-                    bitmapToRecycle.recycle();
-                }
                 // Once saved, we won't use the original icon information, so null it out.
                 shortcut.clearIcon();
             }
@@ -1196,7 +1208,7 @@
     // so override in unit tests.
     // TODO CTS this case.
     void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
-        if (!shortcut.getPackageName().equals(icon.getResPackage())) {
+        if (!shortcut.getPackage().equals(icon.getResPackage())) {
             throw new IllegalArgumentException(
                     "Icon resource must reside in shortcut owner package");
         }
@@ -1353,10 +1365,9 @@
      */
     private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
         Preconditions.checkNotNull(shortcut, "Null shortcut detected");
-        if (shortcut.getActivityComponent() != null) {
+        if (shortcut.getActivity() != null) {
             Preconditions.checkState(
-                    shortcut.getPackageName().equals(
-                            shortcut.getActivityComponent().getPackageName()),
+                    shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
                     "Activity package name mismatch");
         }
 
@@ -1399,7 +1410,7 @@
         }
     }
 
-    private static void validateForXml(String s) {
+    private static void validateForXml(CharSequence s) {
         if (TextUtils.isEmpty(s)) {
             return;
         }
@@ -1427,6 +1438,8 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
             // Throttling.
             if (!ps.tryApiCall()) {
                 return false;
@@ -1444,7 +1457,7 @@
             // Then, add/update all.  We need to make sure to take over "pinned" flag.
             for (int i = 0; i < size; i++) {
                 final ShortcutInfo newShortcut = newShortcuts.get(i);
-                ps.addDynamicShortcut(newShortcut);
+                ps.addOrUpdateDynamicShortcut(newShortcut);
             }
         }
         packageShortcutsChanged(packageName, userId);
@@ -1462,6 +1475,8 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
             // Throttling.
             if (!ps.tryApiCall()) {
                 return false;
@@ -1473,11 +1488,22 @@
 
                 final ShortcutInfo target = ps.findShortcutById(source.getId());
                 if (target != null) {
+                    if (target.isEnabled() != source.isEnabled()) {
+                        Slog.w(TAG,
+                                "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
+                    }
+
                     final boolean replacingIcon = (source.getIcon() != null);
                     if (replacingIcon) {
                         removeIcon(userId, target);
                     }
 
+                    if (source.getActivity() != null &&
+                            !source.getActivity().equals(target.getActivity())) {
+                        // TODO When activity is changing, check the dynamic count.
+                    }
+
+                    // Note copyNonNullFieldsFrom() does the "udpatable with?" check too.
                     target.copyNonNullFieldsFrom(source);
 
                     if (replacingIcon) {
@@ -1502,6 +1528,8 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
             // Throttling.
             if (!ps.tryApiCall()) {
                 return false;
@@ -1513,7 +1541,7 @@
                 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
 
                 // Add it.
-                ps.addDynamicShortcut(newShortcut);
+                ps.addOrUpdateDynamicShortcut(newShortcut);
             }
         }
         packageShortcutsChanged(packageName, userId);
@@ -1528,7 +1556,32 @@
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
         synchronized (mLock) {
-            // TODO implement it.
+            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+
+            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+                ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
+                        disabledMessage, disabledMessageResId,
+                        /* overrideImmutable=*/ false);
+            }
+        }
+        packageShortcutsChanged(packageName, userId);
+    }
+
+    @Override
+    public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+        Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+
+        synchronized (mLock) {
+            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+
+            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+                ps.enableWithId((String) shortcutIds.get(i));
+            }
         }
         packageShortcutsChanged(packageName, userId);
     }
@@ -1540,8 +1593,12 @@
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
         synchronized (mLock) {
+            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-                getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(
+                ps.deleteDynamicWithId(
                         Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
             }
         }
@@ -1570,6 +1627,17 @@
     }
 
     @Override
+    public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+        synchronized (mLock) {
+            return getShortcutsWithQueryLocked(
+                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
+                    ShortcutInfo::isManifestShortcut);
+        }
+    }
+
+    @Override
     public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
@@ -1883,18 +1951,24 @@
                             return false;
                         }
                         if (componentName != null) {
-                            if (si.getActivityComponent() != null
-                                    && !si.getActivityComponent().equals(componentName)) {
+                            if (si.getActivity() != null
+                                    && !si.getActivity().equals(componentName)) {
                                 return false;
                             }
                         }
-                        final boolean matchDynamic =
-                                ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
-                                        && si.isDynamic();
-                        final boolean matchPinned =
-                                ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
-                                        && si.isPinned();
-                        return matchDynamic || matchPinned;
+                        if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
+                                && si.isDynamic()) {
+                            return true;
+                        }
+                        if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
+                                && si.isPinned()) {
+                            return true;
+                        }
+                        if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
+                                && si.isManifestShortcut()) {
+                            return true;
+                        }
+                        return false;
                     }, cloneFlag, callingPackage, launcherUserId);
         }
 
@@ -1967,7 +2041,7 @@
                 final ShortcutInfo si = getShortcutInfoLocked(
                         launcherUserId, callingPackage, packageName, shortcutId, userId);
                 // "si == null" should suffice here, but check the flags too just to make sure.
-                if (si == null || !(si.isDynamic() || si.isPinned())) {
+                if (si == null || !si.isEnabled() || !si.isAlive()) {
                     return null;
                 }
                 return si.getIntent();
@@ -2114,31 +2188,37 @@
         if (DEBUG) {
             Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
         }
-        final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
 
-        synchronized (mLock) {
-            final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
+        final long start = injectElapsedRealtime();
+        try {
+            final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
 
-            user.forAllPackageItems(spi -> {
-                if (spi.getPackageInfo().isShadow()) {
-                    return; // Don't delete shadow information.
+            synchronized (mLock) {
+                final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
+
+                // Find packages that have been uninstalled.
+                user.forAllPackageItems(spi -> {
+                    if (spi.getPackageInfo().isShadow()) {
+                        return; // Don't delete shadow information.
+                    }
+                    if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
+                        gonePackages.add(PackageWithUser.of(spi));
+                    }
+                });
+                if (gonePackages.size() > 0) {
+                    for (int i = gonePackages.size() - 1; i >= 0; i--) {
+                        final PackageWithUser pu = gonePackages.get(i);
+                        cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
+                    }
                 }
-                final int versionCode = getApplicationVersionCode(
-                        spi.getPackageName(), spi.getPackageUserId());
-                if (versionCode >= 0) {
-                    // Package still installed, see if it's updated.
-                    getUserShortcutsLocked(ownerUserId).handlePackageUpdated(
-                            spi.getPackageName(), versionCode);
-                } else {
-                    gonePackages.add(PackageWithUser.of(spi));
-                }
-            });
-            if (gonePackages.size() > 0) {
-                for (int i = gonePackages.size() - 1; i >= 0; i--) {
-                    final PackageWithUser pu = gonePackages.get(i);
-                    cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
-                }
+
+                // Then for each installed app, publish manifest shortcuts when needed.
+                forInstalledApplications(ownerUserId, ai -> {
+                    user.handlePackageAddedOrUpdated(ai.packageName);
+                });
             }
+        } finally {
+            logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
         }
     }
 
@@ -2147,8 +2227,9 @@
             Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
         }
         synchronized (mLock) {
-            forEachLoadedUserLocked(user ->
-                    user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
+            final ShortcutUser user = getUserShortcutsLocked(userId);
+            user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
+            user.handlePackageAddedOrUpdated(packageName);
         }
     }
 
@@ -2158,14 +2239,12 @@
                     packageName, userId));
         }
         synchronized (mLock) {
-            forEachLoadedUserLocked(user ->
-                    user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
+            final ShortcutUser user = getUserShortcutsLocked(userId);
+            user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
 
-            final int versionCode = getApplicationVersionCode(packageName, userId);
-            if (versionCode < 0) {
-                return; // shouldn't happen
+            if (isPackageInstalled(packageName, userId)) {
+                user.handlePackageAddedOrUpdated(packageName);
             }
-            getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode);
         }
     }
 
@@ -2187,10 +2266,16 @@
 
     // === PackageManager interaction ===
 
+    @Nullable
     PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
         return injectPackageInfo(packageName, userId, true);
     }
 
+    @Nullable
+    PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
+        return injectPackageInfo(packageName, userId, false);
+    }
+
     int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
         final long token = injectClearCallingIdentity();
         try {
@@ -2205,6 +2290,7 @@
         }
     }
 
+    @Nullable
     @VisibleForTesting
     PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
             boolean getSignatures) {
@@ -2227,6 +2313,7 @@
         }
     }
 
+    @Nullable
     @VisibleForTesting
     ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
         final long start = injectElapsedRealtime();
@@ -2244,6 +2331,61 @@
         }
     }
 
+    @Nullable
+    @VisibleForTesting
+    PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
+        final long start = injectElapsedRealtime();
+        final long token = injectClearCallingIdentity();
+        try {
+            return mIPackageManager.getPackageInfo(packageName,
+                    PACKAGE_MATCH_FLAGS | PackageManager.GET_ACTIVITIES
+                            | PackageManager.GET_META_DATA, userId);
+        } catch (RemoteException e) {
+            // Shouldn't happen.
+            Slog.wtf(TAG, "RemoteException", e);
+            return null;
+        } finally {
+            injectRestoreCallingIdentity(token);
+
+            logDurationStat(Stats.GET_ACTIVITIES_WITH_METADATA, start);
+        }
+    }
+
+    @Nullable
+    @VisibleForTesting
+    List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) {
+        final long start = injectElapsedRealtime();
+        final long token = injectClearCallingIdentity();
+        try {
+            final ParceledListSlice<ApplicationInfo> parceledList =
+                    mIPackageManager.getInstalledApplications(PACKAGE_MATCH_FLAGS, userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            // Shouldn't happen.
+            Slog.wtf(TAG, "RemoteException", e);
+            return null;
+        } finally {
+            injectRestoreCallingIdentity(token);
+
+            logDurationStat(Stats.GET_INSTALLED_APPLICATIONS, start);
+        }
+    }
+
+    private void forInstalledApplications(@UserIdInt int userId,
+            Consumer<ApplicationInfo> callback) {
+        final List<ApplicationInfo> list = injectInstalledApplications(userId);
+        for (int i = list.size() - 1; i >= 0; i--) {
+            final ApplicationInfo ai = list.get(i);
+
+            if ((ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
+                callback.accept(ai);
+            }
+        }
+    }
+
     private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
         final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
         return (ai != null) && ((ai.flags & flags) == flags);
@@ -2253,15 +2395,10 @@
         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
     }
 
-    /**
-     * @return the version code of the package, or -1 if the app is not installed.
-     */
-    int getApplicationVersionCode(String packageName, int userId) {
-        final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
-        if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
-            return -1;
-        }
-        return ai.versionCode;
+    @Nullable
+    XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+// TODO Doesn't seem like ACROSS_USER is needed, but double check.
+        return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
     }
 
     // === Backup & restore ===
@@ -2402,8 +2539,10 @@
                 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
                 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
                 dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
-
                 dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
+                dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata");
+                dumpStatLS(pw, p, Stats.GET_INSTALLED_APPLICATIONS, "getInstalledApplications");
+                dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
             }
 
             for (int i = 0; i < mUsers.size(); i++) {
@@ -2783,12 +2922,19 @@
     }
 
     @VisibleForTesting
-    ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
+    ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
         synchronized (mLock) {
             final ShortcutUser user = mUsers.get(userId);
             if (user == null) return null;
 
-            final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName);
+            return user.getAllPackagesForTest().get(packageName);
+        }
+    }
+
+    @VisibleForTesting
+    ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
+        synchronized (mLock) {
+            final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
             if (pkg == null) return null;
 
             return pkg.findShortcutById(shortcutId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index d38cfba..840c3df 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
@@ -40,6 +41,8 @@
 
 /**
  * User information used by {@link ShortcutService}.
+ *
+ * All methods should be guarded by {@code #mService.mLock}.
  */
 class ShortcutUser {
     private static final String TAG = ShortcutService.TAG;
@@ -230,19 +233,16 @@
         }
     }
 
-    /**
-     * Called when a package is updated.
-     */
-    public void handlePackageUpdated(@NonNull String packageName,
-            int newVersionCode) {
-        if (!mPackages.containsKey(packageName)) {
-            return;
+    public void handlePackageAddedOrUpdated(@NonNull String packageName) {
+        final boolean isNewApp = !mPackages.containsKey(packageName);
+
+        final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
+
+        if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) {
+            if (isNewApp) {
+                mPackages.remove(packageName);
+            }
         }
-        final ShortcutPackage p = getPackageShortcutsIfExists(packageName);
-        if (p == null) {
-            return; // No need to instantiate ShortcutPackage.
-        }
-        p.handlePackageUpdated(newVersionCode);
     }
 
     public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
new file mode 100644
index 0000000..2f9d06c
--- /dev/null
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="shortcut_title1"></string>
+    <string name="shortcut_text1"></string>
+    <string name="shortcut_disabled_message1"></string>
+    <string name="shortcut_title2"></string>
+    <string name="shortcut_text2"></string>
+    <string name="shortcut_disabled_message2"></string>
+</resources>
diff --git a/services/tests/servicestests/res/xml/shortcut_0.xml b/services/tests/servicestests/res/xml/shortcut_0.xml
new file mode 100644
index 0000000..fda001e
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_0.xml
@@ -0,0 +1,2 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1.xml b/services/tests/servicestests/res/xml/shortcut_1.xml
new file mode 100644
index 0000000..c370e74
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_1.xml
@@ -0,0 +1,14 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:shortcutRank="1"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutText="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+        android:shortcutIntentAction="action1"
+        android:shortcutIntentData="data1"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1_disable.xml b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
new file mode 100644
index 0000000..08ecac3
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="false"
+        android:shortcutRank="1"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutTitle="@string/shortcut_title2"
+        android:shortcutText="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_2.xml b/services/tests/servicestests/res/xml/shortcut_2.xml
new file mode 100644
index 0000000..9e923f3
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_2.xml
@@ -0,0 +1,26 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:shortcutRank="1"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutText="@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/"
+    />
+    <shortcut
+        android:shortcutId="ms2"
+        android:enabled="true"
+        android:shortcutRank="2"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutTitle="@string/shortcut_title2"
+        android:shortcutText="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        android:shortcutCategories="android.shortcut.conversation"
+        android:shortcutIntentAction="action2"
+        android:shortcutIntentData="http://a.b.c/2"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
new file mode 100644
index 0000000..d90c18d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
@@ -0,0 +1,12 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="action1"
+    />
+    <shortcut
+        android:shortcutId="ms1"
+        android:shortcutTitle="@string/shortcut_title2"
+        android:shortcutIntentAction="action2"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5.xml b/services/tests/servicestests/res/xml/shortcut_5.xml
new file mode 100644
index 0000000..f3f71d2
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5.xml
@@ -0,0 +1,40 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:shortcutRank="1"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutText="@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"
+    />
+    <shortcut
+        android:shortcutId="ms2"
+        android:enabled="true"
+        android:shortcutRank="2"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutTitle="@string/shortcut_title2"
+        android:shortcutText="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        android:shortcutCategories="android.shortcut.conversation"
+        android:shortcutIntentAction="action2"
+    />
+    <shortcut
+        android:shortcutId="ms3"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms4"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms5"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_1.xml b/services/tests/servicestests/res/xml/shortcut_error_1.xml
new file mode 100644
index 0000000..2c51420
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_1.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="x1"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_2.xml b/services/tests/servicestests/res/xml/shortcut_error_2.xml
new file mode 100644
index 0000000..d075e7d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_2.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="manifest-shortcut-3"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="x2"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_3.xml b/services/tests/servicestests/res/xml/shortcut_error_3.xml
new file mode 100644
index 0000000..30bf56e
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_3.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="manifest-shortcut-3"
+        android:shortcutTitle="@string/shortcut_title1"
+    />
+    <shortcut
+        android:shortcutId="x3"
+        android:shortcutTitle="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 8a2a58c..1a0a003 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -15,17 +15,22 @@
  */
 package com.android.server.pm;
 
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDisabled;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamic;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamicOrPinned;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllEnabled;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIcon;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconFile;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconResId;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIntents;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveTitle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllImmutable;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllKeyFieldsOnly;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllManifest;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveIntents;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveTitle;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotKeyFieldsOnly;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotManifest;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllPinned;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllStringsResolved;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllUnique;
@@ -33,8 +38,10 @@
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundleEmpty;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackNotReceived;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackReceived;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCannotUpdateImmutable;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicAndPinned;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicOnly;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.findShortcut;
@@ -70,6 +77,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ILauncherApps;
 import android.content.pm.LauncherApps;
@@ -83,6 +91,7 @@
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
@@ -143,6 +152,11 @@
  adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest \
  -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
 
+
+ * TODO More tests for pinning + manifest shortcuts
+ * TODO Manifest shortcuts + app upgrade -> launcher callback.
+ *      Also locale change should trigger launcehr callbacks too, when they use strign resoucres.
+ *      (not implemented yet.)
  * TODO: Add checks with assertAllNotHaveIcon()
  * TODO: Detailed test for hasShortcutPermissionInner().
  * TODO: Add tests for the command line functions too.
@@ -155,9 +169,9 @@
      * Whether to enable dump or not.  Should be only true when debugging to avoid bugs where
      * dump affecting the behavior.
      */
-    private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
+    private static final boolean ENABLE_DUMP = true; // DO NOT SUBMIT WITH true
 
-    private static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
+    private static final boolean DUMP_IN_TEARDOWN = true; // DO NOT SUBMIT WITH true
 
     private static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
 
@@ -229,6 +243,15 @@
         public int getUserId() {
             return UserHandle.USER_SYSTEM;
         }
+
+        public PackageInfo injectGetActivitiesWithMetadata(
+                String packageName, @UserIdInt int userId) {
+            return ShortcutManagerTest.this.injectGetActivitiesWithMetadata(packageName, userId);
+        }
+
+        public XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+            return ShortcutManagerTest.this.injectXmlMetaData(activityInfo, key);
+        }
     }
 
     /** ShortcutService with injection override methods. */
@@ -339,6 +362,21 @@
         }
 
         @Override
+        List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) {
+            return getInstalledApplications(userId);
+        }
+
+        @Override
+        PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
+            return mContext.injectGetActivitiesWithMetadata(packageName, userId);
+        }
+
+        @Override
+        XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+            return mContext.injectXmlMetaData(activityInfo, key);
+        }
+
+        @Override
         void postToHandler(Runnable r) {
             final long token = mContext.injectClearCallingIdentity();
             r.run();
@@ -524,6 +562,9 @@
 
     private final ArrayList<String> mCallerPermissions = new ArrayList<>();
 
+    private final HashMap<String, HashMap<ComponentName, Integer>> mActivityMetadataResId
+            = new HashMap<>();
+
     static {
         QUERY_ALL.setQueryFlags(
                 ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
@@ -720,6 +761,12 @@
         });
     }
 
+    private void updatePackageLastUpdateTime(String packageName, long increment) {
+        updatePackageInfo(packageName, pi -> {
+            pi.lastUpdateTime += increment;
+        });
+    }
+
     private void uninstallPackage(int userId, String packageName) {
         if (ENABLE_DUMP) {
             Log.i(TAG, "Unnstall package " + packageName + " / " + userId);
@@ -742,8 +789,12 @@
         final PackageInfo ret = new PackageInfo();
         ret.packageName = pi.packageName;
         ret.versionCode = pi.versionCode;
+        ret.lastUpdateTime = pi.lastUpdateTime;
+
         ret.applicationInfo = new ApplicationInfo(pi.applicationInfo);
         ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid);
+        ret.applicationInfo.packageName = pi.packageName;
+
         if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
             ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
         }
@@ -755,6 +806,66 @@
         return ret;
     }
 
+    private void addApplicationInfo(PackageInfo pi, List<ApplicationInfo> list) {
+        if (pi != null && pi.applicationInfo != null) {
+            list.add(pi.applicationInfo);
+        }
+    }
+
+    private List<ApplicationInfo> getInstalledApplications(int userId) {
+        final ArrayList<ApplicationInfo> ret = new ArrayList<>();
+
+        addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret);
+        addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret);
+        addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret);
+        addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret);
+        addApplicationInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret);
+        addApplicationInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret);
+        addApplicationInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret);
+        addApplicationInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret);
+
+        return ret;
+    }
+
+    private void addManifestShortcutResource(ComponentName activity, int resId) {
+        final String packageName = activity.getPackageName();
+        HashMap<ComponentName, Integer> map = mActivityMetadataResId.get(packageName);
+        if (map == null) {
+            map = new HashMap<>();
+            mActivityMetadataResId.put(packageName, map);
+        }
+        map.put(activity, resId);
+    }
+
+    private PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
+        final PackageInfo ret = getInjectedPackageInfo(packageName, userId,
+                /* getSignatures=*/ false);
+
+        final HashMap<ComponentName, Integer> activities = mActivityMetadataResId.get(packageName);
+        if (activities != null) {
+            final ArrayList<ActivityInfo> list = new ArrayList<>();
+
+            for (ComponentName cn : activities.keySet()) {
+                ActivityInfo ai = new ActivityInfo();
+                ai.packageName = cn.getPackageName();
+                ai.name = cn.getClassName();
+                ai.metaData = new Bundle();
+                ai.metaData.putInt(ShortcutParser.METADATA_KEY, activities.get(cn));
+                list.add(ai);
+            }
+            ret.activities = list.toArray(new ActivityInfo[list.size()]);
+        }
+        return ret;
+    }
+
+    private XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+        if (!ShortcutParser.METADATA_KEY.equals(key) || activityInfo.metaData == null) {
+            return null;
+        }
+        final int resId = activityInfo.metaData.getInt(key);
+        return getTestContext().getResources().getXml(resId);
+    }
+
     /** Replace the current calling package */
     private void setCaller(String packageName, int userId) {
         mInjectedClientPackage = packageName;
@@ -883,6 +994,12 @@
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
     }
 
+    private ShortcutInfo makeShortcutWithTitle(String id, String title) {
+        return makeShortcut(
+                id, title, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
+    }
+
     /**
      * Make a shortcut with an ID and timestamp.
      */
@@ -949,7 +1066,7 @@
             Icon icon, Intent intent, int rank) {
         final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext)
                 .setId(id)
-                .setActivityComponent(new ComponentName(mClientContext.getPackageName(), "dummy"))
+                .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy"))
                 .setTitle(title)
                 .setRank(rank)
                 .setIntent(intent);
@@ -957,7 +1074,7 @@
             b.setIcon(icon);
         }
         if (activity != null) {
-            b.setActivityComponent(activity);
+            b.setActivity(activity);
         }
         final ShortcutInfo s = b.build();
 
@@ -1126,8 +1243,10 @@
         return new File(si.getBitmapPath()).getName();
     }
 
-    private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) {
-        return getPackageShortcut(packageName, shortcutId, getCallingUserId());
+    private List<ShortcutInfo> getCallerShortcuts() {
+        final ShortcutPackage p = mService.getPackageShortcutForTest(
+                getCallingPackage(), getCallingUserId());
+        return p == null ? null : p.getAllShortcutsForTest();
     }
 
     private ShortcutInfo getCallerShortcut(String shortcutId) {
@@ -1157,6 +1276,13 @@
         return infoList.get(0);
     }
 
+    private Intent genPackageAddIntent(String pakcageName, int userId) {
+        Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        i.setData(Uri.parse("package:" + pakcageName));
+        i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        return i;
+    }
+
     private Intent genPackageDeleteIntent(String pakcageName, int userId) {
         Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED);
         i.setData(Uri.parse("package:" + pakcageName));
@@ -1291,9 +1417,9 @@
 
     // === Test for app side APIs ===
 
-    /** Test for {@link android.content.pm.ShortcutManager#getMaxDynamicShortcutCount()} */
+    /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */
     public void testGetMaxDynamicShortcutCount() {
-        assertEquals(MAX_SHORTCUTS, mManager.getMaxDynamicShortcutCount());
+        assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity());
     }
 
     /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */
@@ -1301,6 +1427,11 @@
         assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount());
     }
 
+    public void getIconMaxDimensions() {
+        assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxWidth());
+        assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxHeight());
+    }
+
     /** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */
     public void testGetRateLimitResetTime() {
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
@@ -2393,7 +2524,7 @@
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
                     .setId("id")
-                    .setActivityComponent(new ComponentName(mClientContext, "dummy"))
+                    .setActivity(new ComponentName(mClientContext, "dummy"))
                     .setTitleResId(10)
                     .setTextResId(11)
                     .setDisabledMessageResId(12)
@@ -2404,7 +2535,7 @@
         runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
             ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
                     .setId("id")
-                    .setActivityComponent(new ComponentName(mClientContext, "dummy"))
+                    .setActivity(new ComponentName(mClientContext, "dummy"))
                     .setTitleResId(10)
                     .setTextResId(11)
                     .setDisabledMessageResId(12)
@@ -2514,7 +2645,6 @@
     }
 
     public void testPinShortcutAndGetPinnedShortcuts() {
-        // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
             final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -2551,38 +2681,155 @@
             assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
             mManager.removeDynamicShortcuts(list("s2"));
             assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+
+            assertShortcutIds(mManager.getDynamicShortcuts(), "s1");
         });
 
         runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
             assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
             mManager.removeDynamicShortcuts(list("s3"));
             assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+
+            assertShortcutIds(mManager.getDynamicShortcuts(), "s2", "s4");
         });
 
         runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
             assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
             mManager.removeDynamicShortcuts(list("s2"));
             assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+
+            assertEmpty(mManager.getDynamicShortcuts());
         });
 
         // Get pinned shortcuts from launcher
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
-            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
-                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
                     "s2");
 
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
+                    "s3", "s4");
+
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_3,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))))
+                    /* none */);
+        });
+    }
+
+    /**
+     * This is similar to the above test, except it used "disable" instead of "remove".  It also
+     * does "enable".
+     */
+    public void testDisableAndEnableShortcuts() {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
+            final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
+
+            assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
+            final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000);
+            final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500);
+            assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s2", 1000);
+            assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
+        });
+
+        // Pin some.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2", "s3"), getCallingUser());
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s3", "s4", "s5"), getCallingUser());
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3,
+                    list("s3"), getCallingUser());  // Note ID doesn't exist
+        });
+
+        // Disable some.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+
+            mManager.disableShortcuts(list("s2"));
+
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+            assertShortcutIds(mManager.getDynamicShortcuts(), "s1");
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+
+            // disable should work even if a shortcut is not dynamic, so try calling "remove" first
+            // here.
+            mManager.removeDynamicShortcuts(list("s3"));
+            mManager.disableShortcuts(list("s3"));
+
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+            assertShortcutIds(mManager.getDynamicShortcuts(), "s2", "s4");
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+
+            mManager.disableShortcuts(list("s2"));
+
+            assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+
+            assertEmpty(mManager.getDynamicShortcuts());
+            assertEmpty(getCallerShortcuts());
+        });
+
+        // Get pinned shortcuts from launcher
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllDisabled(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
+                    "s2");
+            assertFalse(mLauncherApps.startShortcut(
+                    CALLING_PACKAGE_1, "s2", null, null, HANDLE_USER_0));
+
             assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
                     /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
                     "s3", "s4");
+            assertFalse(mLauncherApps.startShortcut(
+                    CALLING_PACKAGE_2, "s3", null, null, HANDLE_USER_0));
+            assertTrue(mLauncherApps.startShortcut(
+                    CALLING_PACKAGE_2, "s4", null, null, HANDLE_USER_0));
 
-            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_3,
-                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))))
                     /* none */);
         });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.enableShortcuts(list("s2"));
+
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+            assertShortcutIds(mManager.getDynamicShortcuts(), "s1");
+        });
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
+                    "s2");
+            assertTrue(mLauncherApps.startShortcut(
+                    CALLING_PACKAGE_1, "s2", null, null, HANDLE_USER_0));
+        });
     }
 
     public void testPinShortcutAndGetPinnedShortcuts_multi() {
@@ -6007,14 +6254,14 @@
                 () -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
         assertExpectException(
                 NullPointerException.class,
-                "activityComponent must be provided",
+                "activity must be provided",
                 () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()
                         .enforceMandatoryFields());
         assertExpectException(
                 IllegalArgumentException.class,
                 "title must be provided",
                 () -> new ShortcutInfo.Builder(getTestContext()).setId("id")
-                        .setActivityComponent(
+                        .setActivity(
                                 new ComponentName(getTestContext().getPackageName(), "s"))
                         .build()
                         .enforceMandatoryFields());
@@ -6022,7 +6269,7 @@
                 NullPointerException.class,
                 "Intent must be provided",
                 () -> new ShortcutInfo.Builder(getTestContext()).setId("id")
-                        .setActivityComponent(
+                        .setActivity(
                                 new ComponentName(getTestContext().getPackageName(), "s"))
                         .setTitle("x").build()
                         .enforceMandatoryFields());
@@ -6035,7 +6282,7 @@
                 .setTitle("title")
                 .setIntent(makeIntent("action", ShortcutActivity.class))
                 .build());
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals(USER_10, si.getUserId());
         assertEquals(HANDLE_USER_10, si.getUserHandle());
         assertEquals("id", si.getId());
@@ -6047,7 +6294,7 @@
 
         si = new ShortcutInfo.Builder(getTestContext())
                 .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
+                .setActivity(new ComponentName("a", "b"))
                 .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitle("title")
                 .setText("text")
@@ -6063,9 +6310,9 @@
 
         si = parceled(si);
 
-        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(123, si.getIcon().getResId());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
@@ -6090,7 +6337,7 @@
 
         si = new ShortcutInfo.Builder(getTestContext())
                 .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
+                .setActivity(new ComponentName("a", "b"))
                 .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitleResId(10)
                 .setTextResId(11)
@@ -6106,9 +6353,9 @@
 
         si = parceled(si);
 
-        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(123, si.getIcon().getResId());
         assertEquals(10, si.getTitleResId());
         assertEquals(11, si.getTextResId());
@@ -6131,7 +6378,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                 .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
+                .setActivity(new ComponentName("a", "b"))
                 .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitle("title")
                 .setText("text")
@@ -6149,9 +6396,9 @@
 
         assertEquals(USER_11, si.getUserId());
         assertEquals(HANDLE_USER_11, si.getUserHandle());
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(123, si.getIcon().getResId());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
@@ -6168,9 +6415,9 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
 
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
@@ -6188,9 +6435,9 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
 
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
@@ -6207,9 +6454,9 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
 
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(null, si.getActivityComponent());
+        assertEquals(null, si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals(null, si.getTitle());
         assertEquals(null, si.getText());
@@ -6232,7 +6479,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                 .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
+                .setActivity(new ComponentName("a", "b"))
                 .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitleResId(10)
                 .setTextResId(11)
@@ -6250,9 +6497,9 @@
 
         assertEquals(USER_11, si.getUserId());
         assertEquals(HANDLE_USER_11, si.getUserHandle());
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(123, si.getIcon().getResId());
         assertEquals(10, si.getTitleResId());
         assertEquals(11, si.getTextResId());
@@ -6269,9 +6516,9 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
 
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals(10, si.getTitleResId());
         assertEquals(11, si.getTextResId());
@@ -6289,9 +6536,9 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
 
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals(10, si.getTitleResId());
         assertEquals(11, si.getTextResId());
@@ -6308,9 +6555,9 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
 
-        assertEquals(mClientContext.getPackageName(), si.getPackageName());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(null, si.getActivityComponent());
+        assertEquals(null, si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals(0, si.getTitleResId());
         assertEquals(0, si.getTextResId());
@@ -6336,7 +6583,7 @@
                 .build();
         ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
 
-        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
         assertEquals("title", si.getTitle());
         assertEquals("action", si.getIntent().getAction());
@@ -6344,7 +6591,7 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
 
-        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
         assertEquals("title", si.getTitle());
         assertEquals("action", si.getIntent().getAction());
@@ -6352,7 +6599,7 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
 
-        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
         assertEquals("title", si.getTitle());
         assertEquals(null, si.getIntent());
@@ -6360,7 +6607,7 @@
 
         si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
 
-        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
         assertEquals(null, si.getTitle());
         assertEquals(null, si.getIntent());
@@ -6372,7 +6619,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
                 .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
+                .setActivity(new ComponentName("a", "b"))
                 .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitle("title")
                 .setText("text")
@@ -6390,9 +6637,9 @@
 
         si = sorig.clone(/* flags=*/ 0);
         si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
-                .setActivityComponent(new ComponentName("x", "y")).build());
+                .setActivity(new ComponentName("x", "y")).build());
         assertEquals("text", si.getText());
-        assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+        assertEquals(new ComponentName("x", "y"), si.getActivity());
 
         si = sorig.clone(/* flags=*/ 0);
         si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
@@ -6500,7 +6747,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
                 .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
+                .setActivity(new ComponentName("a", "b"))
                 .setIcon(Icon.createWithResource(mClientContext, 123))
                 .setTitleResId(10)
                 .setTextResId(11)
@@ -6518,9 +6765,9 @@
 
         si = sorig.clone(/* flags=*/ 0);
         si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
-                .setActivityComponent(new ComponentName("x", "y")).build());
+                .setActivity(new ComponentName("x", "y")).build());
         assertEquals(11, si.getTextResId());
-        assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+        assertEquals(new ComponentName("x", "y"), si.getActivity());
 
         si = sorig.clone(/* flags=*/ 0);
         si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
@@ -6633,7 +6880,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                 .setId("id")
-                .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                 .setIcon(bmp32x32)
                 .setTitle("title")
                 .setText("text")
@@ -6659,9 +6906,9 @@
 
         assertEquals(USER_10, si.getUserId());
         assertEquals(HANDLE_USER_10, si.getUserHandle());
-        assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
         assertEquals(null, si.getIcon());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
@@ -6688,7 +6935,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                 .setId("id")
-                .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                 .setIcon(bmp32x32)
                 .setTitleResId(10)
                 .setTextResId(11)
@@ -6714,9 +6961,9 @@
 
         assertEquals(USER_10, si.getUserId());
         assertEquals(HANDLE_USER_10, si.getUserHandle());
-        assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
         assertEquals(null, si.getIcon());
         assertEquals(10, si.getTitleResId());
         assertEquals(11, si.getTextResId());
@@ -6743,7 +6990,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                 .setId("id")
-                .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                 .setIcon(bmp32x32)
                 .setTitle("title")
                 .setText("text")
@@ -6768,9 +7015,9 @@
         ShortcutInfo si;
         si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
 
-        assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
         assertEquals(null, si.getIcon());
         assertEquals("title", si.getTitle());
         assertEquals("text", si.getText());
@@ -6796,7 +7043,7 @@
         pb.putInt("k", 1);
         ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                 .setId("id")
-                .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                 .setIcon(bmp32x32)
                 .setTitleResId(10)
                 .setTextResId(11)
@@ -6821,9 +7068,9 @@
         ShortcutInfo si;
         si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
 
-        assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
         assertEquals(null, si.getIcon());
         assertEquals(10, si.getTitleResId());
         assertEquals(11, si.getTextResId());
@@ -6849,4 +7096,810 @@
         // Dump after having some icons.
         dumpsysOnLogcat("test1", /* force= */ true);
     }
+
+    public void testManifestShortcut_publishOnUnlockUser() {
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
+                R.xml.shortcut_5);
+
+        // Unlock user-0.
+        mService.handleUnlockUser(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2", "ms3", "ms4", "ms5");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // Try on another user, with some packages uninstalled.
+        uninstallPackage(USER_10, CALLING_PACKAGE_1);
+        uninstallPackage(USER_10, CALLING_PACKAGE_3);
+
+        mService.handleUnlockUser(USER_10);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_10, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // Now change the resources for package 1, and unlock again.
+        // But we still see *old* shortcuts, because the package version and install time
+        // hasn't changed.
+        shutdownServices();
+
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_5);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+
+        initService();
+        mService.handleUnlockUser(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2", "ms3", "ms4", "ms5");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // Do it again, but this time we change the app version, so we do detect the changes.
+        shutdownServices();
+
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        updatePackageLastUpdateTime(CALLING_PACKAGE_3, 1);
+
+        initService();
+        mService.handleUnlockUser(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2", "ms3", "ms4", "ms5");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // Next, try removing all shortcuts, with some of them pinned.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms3"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("ms2"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("ms1"), HANDLE_USER_0);
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2", "ms3", "ms4", "ms5");
+            assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllManifest(
+                    assertAllEnabled(mManager.getPinnedShortcuts())))),
+                    "ms3");
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllManifest(
+                    assertAllEnabled(mManager.getPinnedShortcuts())))),
+                    "ms2");
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllManifest(
+                    assertAllEnabled(mManager.getPinnedShortcuts())))),
+                    "ms1");
+        });
+
+        shutdownServices();
+
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_0);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
+                R.xml.shortcut_0);
+
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        updatePackageVersion(CALLING_PACKAGE_2, 1);
+        updatePackageVersion(CALLING_PACKAGE_3, 1);
+
+        initService();
+        mService.handleUnlockUser(USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllNotManifest(
+                    assertAllDisabled(mManager.getPinnedShortcuts())))),
+                    "ms3");
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllNotManifest(
+                    assertAllDisabled(mManager.getPinnedShortcuts())))),
+                    "ms2");
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllNotManifest(
+                    assertAllDisabled(mManager.getPinnedShortcuts())))),
+                    "ms1");
+        });
+
+        // Make sure we don't have ShortcutPackage for packages that don't have shortcuts.
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_4, USER_0));
+        assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_0));
+    }
+
+
+    public void testManifestShortcut_publishOnBroadcast() {
+        // First, no packages are installed.
+        uninstallPackage(USER_0, CALLING_PACKAGE_1);
+        uninstallPackage(USER_0, CALLING_PACKAGE_2);
+        uninstallPackage(USER_0, CALLING_PACKAGE_3);
+        uninstallPackage(USER_0, CALLING_PACKAGE_4);
+        uninstallPackage(USER_10, CALLING_PACKAGE_1);
+        uninstallPackage(USER_10, CALLING_PACKAGE_2);
+        uninstallPackage(USER_10, CALLING_PACKAGE_3);
+        uninstallPackage(USER_10, CALLING_PACKAGE_4);
+
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_10);
+
+        // Originally no manifest shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // Package 1 updated, with manifest shortcuts.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // Package 2 updated, with manifest shortcuts.
+
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_5);
+        updatePackageVersion(CALLING_PACKAGE_2, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2", "ms3", "ms4", "ms5");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // Package 2 updated, with less manifest shortcuts.
+        // This time we use updatePackageLastUpdateTime() instead of updatePackageVersion().
+
+        dumpsysOnLogcat("Before pinning");
+
+        // Also pin some.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("ms2", "ms3"), HANDLE_USER_0);
+        });
+
+        dumpsysOnLogcat("After pinning");
+
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertShortcutIds(assertAllImmutable(assertAllPinned(
+                    mManager.getPinnedShortcuts())),
+                    "ms2", "ms3");
+            // ms3 is no longer in manifest, so should be disabled.
+            // but ms1 and ms2 should be enabled.
+            assertAllEnabled(list(getCallerShortcut("ms1")));
+            assertAllEnabled(list(getCallerShortcut("ms2")));
+            assertAllDisabled(list(getCallerShortcut("ms3")));
+        });
+
+        // Package 2 on user 10 has no shortcuts yet.
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+        // Send PACKAGE_ADD broadcast to have Package 2 on user-10 publish manifest shortcuts.
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
+
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertEmpty(mManager.getPinnedShortcuts());
+        });
+
+        // But it shouldn't affect user-0.
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+            assertShortcutIds(assertAllImmutable(assertAllPinned(
+                    mManager.getPinnedShortcuts())),
+                    "ms2", "ms3");
+            assertAllEnabled(list(getCallerShortcut("ms1")));
+            assertAllEnabled(list(getCallerShortcut("ms2")));
+            assertAllDisabled(list(getCallerShortcut("ms3")));
+        });
+
+        // Package 2 now has no manifest shortcuts.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_0);
+        updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+        // No manifest shortcuts, and pinned ones are disabled.
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllDisabled(
+                    mManager.getPinnedShortcuts()))),
+                    "ms2", "ms3");
+        });
+    }
+
+    public void testManifestShortcuts_missingMandatoryFields() {
+        // Start with no apps installed.
+        uninstallPackage(USER_0, CALLING_PACKAGE_1);
+        uninstallPackage(USER_0, CALLING_PACKAGE_2);
+        uninstallPackage(USER_0, CALLING_PACKAGE_3);
+        uninstallPackage(USER_0, CALLING_PACKAGE_4);
+
+        mService.handleUnlockUser(USER_0);
+
+        // Make sure no manifest shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+        });
+
+        // Package 1 updated, which has one valid manifest shortcut and one invalid.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_error_1);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Only the valid one is published.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "x1");
+        });
+
+        // Package 1 updated, which has one valid manifest shortcut and one invalid.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_error_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Only the valid one is published.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "x2");
+        });
+
+        // Package 1 updated, which has one valid manifest shortcut and one invalid.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_error_3);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Only the valid one is published.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "x3");
+        });
+    }
+
+    public void testManifestShortcuts_checkAllFields() {
+        mService.handleUnlockUser(USER_0);
+
+        // Package 1 updated, which has one valid manifest shortcut and one invalid.
+        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));
+
+        // Only the valid one is published.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2", "ms3", "ms4", "ms5");
+
+            // check first shortcut.
+            ShortcutInfo si = getCallerShortcut("ms1");
+
+            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.string.shortcut_text1, si.getTextResId());
+            assertEquals(R.string.shortcut_disabled_message1, si.getDisabledMessageResId());
+            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());
+
+            // check another
+            si = getCallerShortcut("ms2");
+
+            assertEquals("ms2", si.getId());
+            assertEquals(R.drawable.icon2, si.getIconResourceId());
+            assertEquals(R.string.shortcut_title2, si.getTitleResId());
+            assertEquals(R.string.shortcut_text2, si.getTextResId());
+            assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResId());
+            assertEquals(set("android.shortcut.conversation"), si.getCategories());
+            assertEquals("action2", si.getIntent().getAction());
+            assertEquals(null, si.getIntent().getData());
+
+            // check another
+            si = getCallerShortcut("ms3");
+
+            assertEquals("ms3", si.getId());
+            assertEquals(0, si.getIconResourceId());
+            assertEquals(R.string.shortcut_title1, si.getTitleResId());
+            assertEquals(0, si.getTextResId());
+            assertEquals(0, si.getDisabledMessageResId());
+            assertEquals(null, si.getCategories());
+            assertEquals("android.intent.action.VIEW", si.getIntent().getAction());
+            assertEquals(null, si.getIntent().getData());
+        });
+    }
+
+    public void testManifestShortcuts_updateAndDisabled_notPinned() {
+        mService.handleUnlockUser(USER_0);
+
+        // First, just publish a manifest shortcut.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Only the valid one is published.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+
+            // Make sure there's no other dangling shortcuts.
+            assertShortcutIds(getCallerShortcuts(), "ms1");
+        });
+
+        // Now version up, the manifest shortcut is disabled now.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1_disable);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Because shortcut 1 wasn't pinned, it'll just go away.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertEmpty(mManager.getPinnedShortcuts());
+
+            // Make sure there's no other dangling shortcuts.
+            assertEmpty(getCallerShortcuts());
+        });
+    }
+
+    public void testManifestShortcuts_updateAndDisabled_pinned() {
+        mService.handleUnlockUser(USER_0);
+
+        // First, just publish a manifest shortcut.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Only the valid one is published.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+            assertEmpty(mManager.getPinnedShortcuts());
+
+            // Make sure there's no other dangling shortcuts.
+            assertShortcutIds(getCallerShortcuts(), "ms1");
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_0);
+        });
+
+        // Now upgrade, the manifest shortcut is disabled now.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1_disable);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Because shortcut 1 was pinned, it'll still exist as pinned, but disabled.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEmpty(mManager.getManifestShortcuts());
+            assertShortcutIds(assertAllNotManifest(assertAllImmutable(assertAllDisabled(
+                    mManager.getPinnedShortcuts()))),
+                    "ms1");
+
+            // Make sure the fields are updated.
+            ShortcutInfo si = getCallerShortcut("ms1");
+
+            assertEquals("ms1", si.getId());
+            assertEquals(R.drawable.icon2, si.getIconResourceId());
+            assertEquals(R.string.shortcut_title2, si.getTitleResId());
+            assertEquals(R.string.shortcut_text2, si.getTextResId());
+            assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResId());
+            assertEquals(Intent.ACTION_VIEW, si.getIntent().getAction());
+
+            // Make sure there's no other dangling shortcuts.
+            assertShortcutIds(getCallerShortcuts(), "ms1");
+        });
+    }
+
+    public void testManifestShortcuts_duplicateInSingleActivity() {
+        mService.handleUnlockUser(USER_0);
+
+        // The XML has two shortcuts with the same ID.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2_duplicate);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1");
+
+            // Make sure the first one has survived.  (the second one has a different title.)
+            ShortcutInfo si = getCallerShortcut("ms1");
+            assertEquals(R.string.shortcut_title1, si.getTitleResId());
+
+            // Make sure there's no other dangling shortcuts.
+            assertShortcutIds(getCallerShortcuts(), "ms1");
+        });
+    }
+
+    public void testManifestShortcuts_duplicateInTwoActivities() {
+        mService.handleUnlockUser(USER_0);
+
+        // ShortcutActivity has shortcut ms1
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+
+        // ShortcutActivity2 has two shortcuts, ms1 and ms2.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
+                R.xml.shortcut_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+
+            // ms1 should belong to ShortcutActivity.
+            ShortcutInfo si = getCallerShortcut("ms1");
+            assertEquals(R.string.shortcut_title1, si.getTitleResId());
+            assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                    si.getActivity());
+
+            // ms2 should belong to ShortcutActivity*2*.
+            si = getCallerShortcut("ms2");
+            assertEquals(R.string.shortcut_title2, si.getTitleResId());
+            assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
+                    si.getActivity());
+
+            // Make sure there's no other dangling shortcuts.
+            assertShortcutIds(getCallerShortcuts(), "ms1", "ms2");
+        });
+    }
+
+    /**
+     * Manifest shortcuts cannot override shortcuts that were published via the APIs.
+     */
+    public void testManifestShortcuts_cannotOverrideNonManifest() {
+        mService.handleUnlockUser(USER_0);
+
+        // Create a non-pinned dynamic shortcut and a non-dynamic pinned shortcut.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.setDynamicShortcuts(list(
+                    makeShortcut("ms1", "title1",
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                    /* icon */ null, new Intent("action1"), /* rank */ 0),
+                    makeShortcut("ms2", "title2",
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                    /* icon */ null, new Intent("action1"), /* rank */ 0)));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2"), HANDLE_USER_0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.removeDynamicShortcuts(list("ms2"));
+
+            assertShortcutIds(mManager.getDynamicShortcuts(), "ms1");
+            assertShortcutIds(mManager.getPinnedShortcuts(), "ms2");
+            assertEmpty(mManager.getManifestShortcuts());
+        });
+
+        // Then update the app with 5 manifest shortcuts.
+        // Make sure "ms1" and "ms2" won't be replaced.
+        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, () -> {
+            assertShortcutIds(assertAllNotManifest(mManager.getDynamicShortcuts()), "ms1");
+            assertShortcutIds(assertAllNotManifest(mManager.getPinnedShortcuts()), "ms2");
+            assertShortcutIds(assertAllManifest(mManager.getManifestShortcuts()),
+                    "ms3", "ms4", "ms5");
+
+            // ms1 and ms2 shouold keep the original title.
+            ShortcutInfo si = getCallerShortcut("ms1");
+            assertEquals("title1", si.getTitle());
+
+            si = getCallerShortcut("ms2");
+            assertEquals("title2", si.getTitle());
+        });
+    }
+
+    private void checkManifestShortcuts_immutable_verify() {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllNotManifest(assertAllEnabled(
+                    mManager.getDynamicShortcuts())),
+                    "s1");
+            assertShortcutIds(assertAllManifest(assertAllEnabled(
+                    mManager.getManifestShortcuts())),
+                    "ms1");
+            assertShortcutIds(assertAllNotManifest(assertAllDisabled(
+                    mManager.getPinnedShortcuts())),
+                    "ms2");
+
+            assertEquals("t1", getCallerShortcut("s1").getTitle());
+
+            // Make sure there are no other shortcuts.
+            assertShortcutIds(getCallerShortcuts(), "s1", "ms1", "ms2");
+        });
+    }
+
+    /**
+     * Make sure the APIs won't work on manifest shortcuts.
+     */
+    public void testManifestShortcuts_immutable() {
+        mService.handleUnlockUser(USER_0);
+
+        // Create a non-pinned manifest shortcut, a pinned shortcut that was originally
+        // a manifest shortcut, as well as a dynamic shortcut.
+
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2"), HANDLE_USER_0);
+        });
+
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.addDynamicShortcuts(list(makeShortcutWithTitle("s1", "t1")));
+        });
+
+        checkManifestShortcuts_immutable_verify();
+
+        // Note that even though the first argument is not immutable and only the second one
+        // is immutable, the first argument should not be executed either.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertCannotUpdateImmutable(() -> {
+                mManager.setDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms1")));
+            });
+            assertCannotUpdateImmutable(() -> {
+                mManager.setDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms2")));
+            });
+        });
+        checkManifestShortcuts_immutable_verify();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertCannotUpdateImmutable(() -> {
+                mManager.addDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms1")));
+            });
+            assertCannotUpdateImmutable(() -> {
+                mManager.addDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms2")));
+            });
+        });
+        checkManifestShortcuts_immutable_verify();
+
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertCannotUpdateImmutable(() -> {
+                mManager.updateShortcuts(list(makeShortcut("s1"), makeShortcut("ms1")));
+            });
+            assertCannotUpdateImmutable(() -> {
+                mManager.updateShortcuts(list(makeShortcut("s1"), makeShortcut("ms2")));
+            });
+        });
+        checkManifestShortcuts_immutable_verify();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertCannotUpdateImmutable(() -> {
+                mManager.removeDynamicShortcuts(list("s1", "ms1"));
+            });
+            assertCannotUpdateImmutable(() -> {
+                mManager.removeDynamicShortcuts(list("s2", "ms2"));
+            });
+        });
+        checkManifestShortcuts_immutable_verify();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertCannotUpdateImmutable(() -> {
+                mManager.disableShortcuts(list("s1", "ms1"));
+            });
+        });
+        checkManifestShortcuts_immutable_verify();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertCannotUpdateImmutable(() -> {
+                mManager.enableShortcuts(list("s1", "ms2"));
+            });
+        });
+        checkManifestShortcuts_immutable_verify();
+    }
 }
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 c23f9e6..7fa8a800 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
@@ -246,11 +246,22 @@
             reset(o);
         }
     }
+
+    public static <T> List<T> assertEmpty(List<T> list) {
+        assertEquals(0, list.size());
+        return list;
+    }
+
     public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
             String expectedExceptionMessageRegex, Runnable r) {
         assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
     }
 
+    public static void assertCannotUpdateImmutable(Runnable r) {
+        assertExpectException(
+                IllegalArgumentException.class, "may not be manipulated via APIs", r);
+    }
+
     public static void assertDynamicShortcutCountExceeded(Runnable r) {
         assertExpectException(IllegalArgumentException.class,
                 "Max number of dynamic shortcuts exceeded", r);
@@ -383,6 +394,46 @@
         return actualShortcuts;
     }
 
+    public static List<ShortcutInfo> assertAllManifest(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.isManifestShortcut());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllNotManifest(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertFalse("ID " + s.getId(), s.isManifestShortcut());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllDisabled(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), !s.isEnabled());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllEnabled(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.isEnabled());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllImmutable(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.isImmutable());
+        }
+        return actualShortcuts;
+    }
+
     public static List<ShortcutInfo> assertAllStringsResolved(
             List<ShortcutInfo> actualShortcuts) {
         for (ShortcutInfo s : actualShortcuts) {
@@ -398,6 +449,7 @@
 
     public static void assertPinnedOnly(ShortcutInfo si) {
         assertFalse(si.isDynamic());
+        assertFalse(si.isManifestShortcut());
         assertTrue(si.isPinned());
     }