Merge \\"Include cause when reporting errors.\\" into nyc-dev am: 60585e6c54
am: be8b6e6164

Change-Id: Id636e7d4e803aafc1aae768e09e9d79cba7522ed
diff --git a/Android.mk b/Android.mk
index 3410765..b08260a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -694,6 +694,7 @@
 	frameworks/base/core/java/android/service/quicksettings/Tile.aidl \
 	frameworks/native/aidl/binder/android/os/PersistableBundle.aidl \
 	system/netd/server/binder/android/net/UidRange.aidl \
+	frameworks/base/telephony/java/android/telephony/PcoData.aidl \
 
 gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
 $(gen): PRIVATE_SRC_FILES := $(aidl_files)
diff --git a/api/current.txt b/api/current.txt
index 70939f8..ea640aa 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -420,7 +420,9 @@
     field public static final int contentInsetStart = 16843859; // 0x1010453
     field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
     field public static final int contextClickable = 16844007; // 0x10104e7
+    field public static final int contextDescription = 16844082; // 0x1010532
     field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
+    field public static final int contextUri = 16844081; // 0x1010531
     field public static final int controlX1 = 16843772; // 0x10103fc
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
@@ -1039,6 +1041,7 @@
     field public static final int rotation = 16843558; // 0x1010326
     field public static final int rotationX = 16843559; // 0x1010327
     field public static final int rotationY = 16843560; // 0x1010328
+    field public static final int roundIcon = 16844080; // 0x1010530
     field public static final int rowCount = 16843637; // 0x1010375
     field public static final int rowDelay = 16843216; // 0x10101d0
     field public static final int rowEdgeFlags = 16843329; // 0x1010241
@@ -1106,11 +1109,20 @@
     field public static final int shareInterpolator = 16843195; // 0x10101bb
     field public static final int sharedUserId = 16842763; // 0x101000b
     field public static final int sharedUserLabel = 16843361; // 0x1010261
+    field public static final int shortcutCategories = 16844077; // 0x101052d
+    field public static final int shortcutDisabledMessage = 16844076; // 0x101052c
+    field public static final int shortcutIcon = 16844073; // 0x1010529
+    field public static final int shortcutId = 16844072; // 0x1010528
+    field public static final int shortcutIntentAction = 16844078; // 0x101052e
+    field public static final int shortcutIntentData = 16844079; // 0x101052f
+    field public static final int shortcutLongLabel = 16844075; // 0x101052b
+    field public static final int shortcutShortLabel = 16844074; // 0x101052a
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
     field public static final int showDividers = 16843561; // 0x1010329
     field public static final int showForAllUsers = 16844015; // 0x10104ef
+    field public static final int showMetadataInPreview = 16844083; // 0x1010533
     field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
     field public static final int showSilent = 16843259; // 0x10101fb
     field public static final int showText = 16843949; // 0x10104ad
@@ -5037,12 +5049,14 @@
     method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder);
     method public java.lang.CharSequence getCancelLabel();
     method public java.lang.CharSequence getConfirmLabel();
+    method public boolean getHintDisplayActionInline();
     method public boolean getHintLaunchesActivity();
     method public java.lang.CharSequence getInProgressLabel();
     method public boolean isAvailableOffline();
     method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean);
     method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
     method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+    method public android.app.Notification.Action.WearableExtender setHintDisplayActionInline(boolean);
     method public android.app.Notification.Action.WearableExtender setHintLaunchesActivity(boolean);
     method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
   }
@@ -5756,7 +5770,10 @@
     method public android.content.pm.ServiceInfo getServiceInfo();
     method public java.lang.String getServiceName();
     method public java.lang.String getSettingsActivity();
+    method public boolean getShowMetadataInPreview();
     method public java.lang.CharSequence loadAuthor(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
+    method public java.lang.CharSequence loadContextDescription(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
+    method public android.net.Uri loadContextUri(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
     method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
@@ -6484,11 +6501,13 @@
     method public android.content.res.Configuration getConfiguration();
     method public int getEventType();
     method public java.lang.String getPackageName();
+    method public java.lang.String getShortcutId();
     method public long getTimeStamp();
     field public static final int CONFIGURATION_CHANGE = 5; // 0x5
     field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
     field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
     field public static final int NONE = 0; // 0x0
+    field public static final int SHORTCUT_INVOCATION = 8; // 0x8
     field public static final int USER_INTERACTION = 7; // 0x7
   }
 
@@ -8191,6 +8210,7 @@
     field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
     field public static final java.lang.String SEARCH_SERVICE = "search";
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
+    field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
     field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
     field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9500,13 +9520,22 @@
 
   public class LauncherApps {
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+    method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
+    method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
+    method public boolean hasShortcutHostPermission();
     method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
     method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
+    method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
     method public void registerCallback(android.content.pm.LauncherApps.Callback);
     method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
     method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
     method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+    method public boolean startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
+    method public boolean startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
     method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
   }
 
@@ -9519,6 +9548,20 @@
     method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
     method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
     method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
+    method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle);
+  }
+
+  public static class LauncherApps.ShortcutQuery {
+    ctor public LauncherApps.ShortcutQuery();
+    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
   }
 
   public class PackageInfo implements android.os.Parcelable {
@@ -10019,6 +10062,74 @@
     field public java.lang.String permission;
   }
 
+  public final class ShortcutInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.content.ComponentName getActivity();
+    method public java.util.Set<java.lang.String> getCategories();
+    method public java.lang.CharSequence getDisabledMessage();
+    method public int getDisabledMessageResourceId();
+    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.CharSequence getLongLabel();
+    method public int getLongLabelResourceId();
+    method public java.lang.String getPackage();
+    method public int getRank();
+    method public java.lang.CharSequence getShortLabel();
+    method public int getShortLabelResourceId();
+    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 isDynamic();
+    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 android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
+  }
+
+  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 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 setExtras(android.os.PersistableBundle);
+    method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
+    method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+    method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setRank(int);
+    method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String);
+  }
+
+  public class ShortcutManager {
+    method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+    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 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();
+    method public void removeAllDynamicShortcuts();
+    method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
+    method public void reportShortcutUsed(java.lang.String);
+    method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+    method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+  }
+
   public class Signature implements android.os.Parcelable {
     ctor public Signature(byte[]);
     ctor public Signature(java.lang.String);
@@ -30640,6 +30751,7 @@
   public static class CallLog.Calls implements android.provider.BaseColumns {
     ctor public CallLog.Calls();
     method public static java.lang.String getLastOutgoingCall(android.content.Context);
+    field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
     field public static final int BLOCKED_TYPE = 6; // 0x6
     field public static final java.lang.String CACHED_FORMATTED_NUMBER = "formatted_number";
     field public static final java.lang.String CACHED_LOOKUP_URI = "lookup_uri";
@@ -30662,6 +30774,7 @@
     field public static final java.lang.String DURATION = "duration";
     field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER";
     field public static final java.lang.String FEATURES = "features";
+    field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
     field public static final int FEATURES_VIDEO = 1; // 0x1
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
@@ -32281,6 +32394,7 @@
     field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
+    field public static final java.lang.String ACTION_DELETION_HELPER_SETTINGS = "android.settings.DELETION_HELPER_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
@@ -32984,6 +33098,7 @@
     field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff
     field public static final java.lang.String SETTINGS_URI = "settings_uri";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
+    field public static final java.lang.String SOURCE_TYPE = "source_type";
     field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri";
   }
 
@@ -35936,9 +36051,14 @@
     method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
+    method public void pullExternalCall();
+    method public final void putExtras(android.os.Bundle);
     method public void registerCallback(android.telecom.Call.Callback);
     method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
+    method public void sendCallEvent(java.lang.String, android.os.Bundle);
     method public void splitFromConference();
     method public void stopDtmfTone();
     method public void swapConference();
@@ -35952,6 +36072,7 @@
     field public static final int STATE_DISCONNECTING = 10; // 0xa
     field public static final int STATE_HOLDING = 3; // 0x3
     field public static final int STATE_NEW = 0; // 0x0
+    field public static final int STATE_PULLING_CALL = 11; // 0xb
     field public static final int STATE_RINGING = 2; // 0x2
     field public static final int STATE_SELECT_PHONE_ACCOUNT = 8; // 0x8
   }
@@ -35962,6 +36083,7 @@
     method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
     method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
     method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
+    method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
@@ -35992,6 +36114,7 @@
     method public static java.lang.String propertiesToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 4194304; // 0x400000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 8388608; // 0x800000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
     field public static final int CAPABILITY_HOLD = 1; // 0x1
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
@@ -36011,7 +36134,9 @@
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
     field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
     field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
+    field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
     field public static final int PROPERTY_WIFI = 8; // 0x8
   }
 
@@ -36062,6 +36187,7 @@
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -36074,6 +36200,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
@@ -36082,14 +36209,18 @@
     method public void onStopDtmfTone();
     method public void onSwap();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
     method public final void removeConnection(android.telecom.Connection);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
     method public final void setActive();
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setConnectionTime(long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setOnHold();
     method public final void setStatusHints(android.telecom.StatusHints);
     method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -36115,6 +36246,7 @@
     method public final android.telecom.Conference getConference();
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final int getState();
@@ -36125,16 +36257,24 @@
     method public void onAnswer(int);
     method public void onAnswer();
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onCallEvent(java.lang.String, android.os.Bundle);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
+    method public void onPullExternalCall();
     method public void onReject();
     method public void onReject(java.lang.String);
     method public void onSeparate();
     method public void onStateChanged(int);
     method public void onStopDtmfTone();
     method public void onUnhold();
+    method public static java.lang.String propertiesToString(int);
+    method public final void putExtras(android.os.Bundle);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
+    method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
     method public final void setAudioModeIsVoip(boolean);
@@ -36142,9 +36282,10 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setInitialized();
     method public final void setInitializing();
     method public final void setNextPostDialChar(char);
@@ -36158,6 +36299,7 @@
     method public static java.lang.String stateToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
     field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
@@ -36175,15 +36317,20 @@
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
     field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
     field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+    field public static final java.lang.String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED";
+    field public static final java.lang.String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
     field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
     field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
     field public static final int STATE_DISCONNECTED = 6; // 0x6
     field public static final int STATE_HOLDING = 5; // 0x5
     field public static final int STATE_INITIALIZING = 0; // 0x0
     field public static final int STATE_NEW = 1; // 0x1
+    field public static final int STATE_PULLING_CALL = 7; // 0x7
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
@@ -36261,7 +36408,9 @@
     method public java.lang.String getReason();
     method public int getTone();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int ANSWERED_ELSEWHERE = 11; // 0xb
     field public static final int BUSY = 7; // 0x7
+    field public static final int CALL_PULLED = 12; // 0xc
     field public static final int CANCELED = 4; // 0x4
     field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa
     field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR;
@@ -36297,6 +36446,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onCallRemoved(android.telecom.Call);
     method public void onCanAddCallChanged(boolean);
+    method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onSilenceRinger();
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
@@ -36419,6 +36569,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
     method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onDestroyed(android.telecom.RemoteConference);
     method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -36437,6 +36588,7 @@
     method public android.telecom.RemoteConference getConference();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public int getConnectionCapabilities();
+    method public int getConnectionProperties();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public int getState();
@@ -36448,6 +36600,7 @@
     method public boolean isVoipAudioMode();
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
+    method public void pullExternalCall();
     method public void registerCallback(android.telecom.RemoteConnection.Callback);
     method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
     method public void reject();
@@ -36464,6 +36617,8 @@
     method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference);
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
+    method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
     method public void onDestroyed(android.telecom.RemoteConnection);
     method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
     method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -36560,6 +36715,7 @@
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
     field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
+    field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -37200,6 +37356,26 @@
     method public void onSubscriptionsChanged();
   }
 
+  public final class TelephonyHistogram implements android.os.Parcelable {
+    ctor public TelephonyHistogram(int, int, int);
+    ctor public TelephonyHistogram(android.telephony.TelephonyHistogram);
+    ctor public TelephonyHistogram(android.os.Parcel);
+    method public void addTimeTaken(int);
+    method public int describeContents();
+    method public int getAverageTime();
+    method public int getBucketCount();
+    method public int[] getBucketCounters();
+    method public int[] getBucketEndPoints();
+    method public int getCategory();
+    method public int getId();
+    method public int getMaxTime();
+    method public int getMinTime();
+    method public int getSampleCount();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.TelephonyHistogram> CREATOR;
+    field public static final int TELEPHONY_CATEGORY_RIL = 1; // 0x1
+  }
+
   public class TelephonyManager {
     method public boolean canChangeDtmfToneLength();
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
@@ -37230,6 +37406,7 @@
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
     method public java.lang.String getSubscriberId();
+    method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public int getVoiceNetworkType();
@@ -40095,7 +40272,10 @@
     method public boolean equals(android.util.DisplayMetrics);
     method public void setTo(android.util.DisplayMetrics);
     method public void setToDefaults();
+    field public static final int DENSITY_260 = 260; // 0x104
     field public static final int DENSITY_280 = 280; // 0x118
+    field public static final int DENSITY_300 = 300; // 0x12c
+    field public static final int DENSITY_340 = 340; // 0x154
     field public static final int DENSITY_360 = 360; // 0x168
     field public static final int DENSITY_400 = 400; // 0x190
     field public static final int DENSITY_420 = 420; // 0x1a4
@@ -41304,6 +41484,10 @@
     field public static final int KEYCODE_FOCUS = 80; // 0x50
     field public static final int KEYCODE_FORWARD = 125; // 0x7d
     field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+    field public static final int KEYCODE_FP_NAV_DOWN = 281; // 0x119
+    field public static final int KEYCODE_FP_NAV_LEFT = 282; // 0x11a
+    field public static final int KEYCODE_FP_NAV_RIGHT = 283; // 0x11b
+    field public static final int KEYCODE_FP_NAV_UP = 280; // 0x118
     field public static final int KEYCODE_FUNCTION = 119; // 0x77
     field public static final int KEYCODE_G = 35; // 0x23
     field public static final int KEYCODE_GRAVE = 68; // 0x44
@@ -44527,6 +44711,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44621,6 +44806,7 @@
     field public static final int IME_NULL = 0; // 0x0
     field public int actionId;
     field public java.lang.CharSequence actionLabel;
+    field public java.lang.String[] contentMimeTypes;
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
@@ -44692,6 +44878,7 @@
     method public abstract java.lang.CharSequence getSelectedText(int);
     method public abstract java.lang.CharSequence getTextAfterCursor(int, int);
     method public abstract java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public abstract boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public abstract boolean performContextMenuAction(int);
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44725,6 +44912,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44737,6 +44925,17 @@
     method public void setTarget(android.view.inputmethod.InputConnection);
   }
 
+  public class InputContentInfo implements android.os.Parcelable {
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
+    method public int describeContents();
+    method public android.net.Uri getContentUri();
+    method public android.content.ClipDescription getDescription();
+    method public android.net.Uri getLinkUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
+  }
+
   public abstract interface InputMethod {
     method public abstract void attachToken(android.os.IBinder);
     method public abstract void bindInput(android.view.inputmethod.InputBinding);
diff --git a/api/system-current.txt b/api/system-current.txt
index 8409ee2..872c8fd 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -527,7 +527,9 @@
     field public static final int contentInsetStart = 16843859; // 0x1010453
     field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
     field public static final int contextClickable = 16844007; // 0x10104e7
+    field public static final int contextDescription = 16844082; // 0x1010532
     field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
+    field public static final int contextUri = 16844081; // 0x1010531
     field public static final int controlX1 = 16843772; // 0x10103fc
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
@@ -1146,6 +1148,7 @@
     field public static final int rotation = 16843558; // 0x1010326
     field public static final int rotationX = 16843559; // 0x1010327
     field public static final int rotationY = 16843560; // 0x1010328
+    field public static final int roundIcon = 16844080; // 0x1010530
     field public static final int rowCount = 16843637; // 0x1010375
     field public static final int rowDelay = 16843216; // 0x10101d0
     field public static final int rowEdgeFlags = 16843329; // 0x1010241
@@ -1217,11 +1220,20 @@
     field public static final int shareInterpolator = 16843195; // 0x10101bb
     field public static final int sharedUserId = 16842763; // 0x101000b
     field public static final int sharedUserLabel = 16843361; // 0x1010261
+    field public static final int shortcutCategories = 16844077; // 0x101052d
+    field public static final int shortcutDisabledMessage = 16844076; // 0x101052c
+    field public static final int shortcutIcon = 16844073; // 0x1010529
+    field public static final int shortcutId = 16844072; // 0x1010528
+    field public static final int shortcutIntentAction = 16844078; // 0x101052e
+    field public static final int shortcutIntentData = 16844079; // 0x101052f
+    field public static final int shortcutLongLabel = 16844075; // 0x101052b
+    field public static final int shortcutShortLabel = 16844074; // 0x101052a
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
     field public static final int showDividers = 16843561; // 0x1010329
     field public static final int showForAllUsers = 16844015; // 0x10104ef
+    field public static final int showMetadataInPreview = 16844083; // 0x1010533
     field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
     field public static final int showSilent = 16843259; // 0x10101fb
     field public static final int showText = 16843949; // 0x10104ad
@@ -5185,12 +5197,14 @@
     method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder);
     method public java.lang.CharSequence getCancelLabel();
     method public java.lang.CharSequence getConfirmLabel();
+    method public boolean getHintDisplayActionInline();
     method public boolean getHintLaunchesActivity();
     method public java.lang.CharSequence getInProgressLabel();
     method public boolean isAvailableOffline();
     method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean);
     method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
     method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+    method public android.app.Notification.Action.WearableExtender setHintDisplayActionInline(boolean);
     method public android.app.Notification.Action.WearableExtender setHintLaunchesActivity(boolean);
     method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
   }
@@ -5904,7 +5918,10 @@
     method public android.content.pm.ServiceInfo getServiceInfo();
     method public java.lang.String getServiceName();
     method public java.lang.String getSettingsActivity();
+    method public boolean getShowMetadataInPreview();
     method public java.lang.CharSequence loadAuthor(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
+    method public java.lang.CharSequence loadContextDescription(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
+    method public android.net.Uri loadContextUri(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
     method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
@@ -6766,11 +6783,13 @@
     method public android.content.res.Configuration getConfiguration();
     method public int getEventType();
     method public java.lang.String getPackageName();
+    method public java.lang.String getShortcutId();
     method public long getTimeStamp();
     field public static final int CONFIGURATION_CHANGE = 5; // 0x5
     field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
     field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
     field public static final int NONE = 0; // 0x0
+    field public static final int SHORTCUT_INVOCATION = 8; // 0x8
     field public static final int USER_INTERACTION = 7; // 0x7
   }
 
@@ -8514,6 +8533,7 @@
     field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
     field public static final java.lang.String SEARCH_SERVICE = "search";
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
+    field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
     field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
     field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9854,13 +9874,22 @@
 
   public class LauncherApps {
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+    method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
+    method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
+    method public boolean hasShortcutHostPermission();
     method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
     method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
+    method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
     method public void registerCallback(android.content.pm.LauncherApps.Callback);
     method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
     method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
     method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+    method public boolean startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
+    method public boolean startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
     method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
   }
 
@@ -9873,6 +9902,20 @@
     method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
     method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
     method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
+    method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle);
+  }
+
+  public static class LauncherApps.ShortcutQuery {
+    ctor public LauncherApps.ShortcutQuery();
+    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
   }
 
   public class PackageInfo implements android.os.Parcelable {
@@ -10443,6 +10486,74 @@
     field public java.lang.String permission;
   }
 
+  public final class ShortcutInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.content.ComponentName getActivity();
+    method public java.util.Set<java.lang.String> getCategories();
+    method public java.lang.CharSequence getDisabledMessage();
+    method public int getDisabledMessageResourceId();
+    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.CharSequence getLongLabel();
+    method public int getLongLabelResourceId();
+    method public java.lang.String getPackage();
+    method public int getRank();
+    method public java.lang.CharSequence getShortLabel();
+    method public int getShortLabelResourceId();
+    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 isDynamic();
+    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 android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
+  }
+
+  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 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 setExtras(android.os.PersistableBundle);
+    method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
+    method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+    method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setRank(int);
+    method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String);
+  }
+
+  public class ShortcutManager {
+    method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+    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 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();
+    method public void removeAllDynamicShortcuts();
+    method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
+    method public void reportShortcutUsed(java.lang.String);
+    method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+    method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+  }
+
   public class Signature implements android.os.Parcelable {
     ctor public Signature(byte[]);
     ctor public Signature(java.lang.String);
@@ -31543,6 +31654,7 @@
     method public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException;
     method public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException;
     method public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
+    method public static void rebootWipeAb(android.content.Context, java.io.File, java.lang.String) throws java.io.IOException;
     method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
     method public static void rebootWipeUserData(android.content.Context) throws java.io.IOException;
     method public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
@@ -33212,6 +33324,7 @@
   public static class CallLog.Calls implements android.provider.BaseColumns {
     ctor public CallLog.Calls();
     method public static java.lang.String getLastOutgoingCall(android.content.Context);
+    field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
     field public static final int BLOCKED_TYPE = 6; // 0x6
     field public static final java.lang.String CACHED_FORMATTED_NUMBER = "formatted_number";
     field public static final java.lang.String CACHED_LOOKUP_URI = "lookup_uri";
@@ -33234,6 +33347,7 @@
     field public static final java.lang.String DURATION = "duration";
     field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER";
     field public static final java.lang.String FEATURES = "features";
+    field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
     field public static final int FEATURES_VIDEO = 1; // 0x1
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
@@ -34985,6 +35099,7 @@
     field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
+    field public static final java.lang.String ACTION_DELETION_HELPER_SETTINGS = "android.settings.DELETION_HELPER_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
@@ -35691,6 +35806,7 @@
     field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff
     field public static final java.lang.String SETTINGS_URI = "settings_uri";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
+    field public static final java.lang.String SOURCE_TYPE = "source_type";
     field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri";
   }
 
@@ -38771,10 +38887,15 @@
     method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
+    method public void pullExternalCall();
+    method public final void putExtras(android.os.Bundle);
     method public void registerCallback(android.telecom.Call.Callback);
     method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
     method public deprecated void removeListener(android.telecom.Call.Listener);
+    method public void sendCallEvent(java.lang.String, android.os.Bundle);
     method public void splitFromConference();
     method public void stopDtmfTone();
     method public void swapConference();
@@ -38789,6 +38910,7 @@
     field public static final int STATE_HOLDING = 3; // 0x3
     field public static final int STATE_NEW = 0; // 0x0
     field public static final deprecated int STATE_PRE_DIAL_WAIT = 8; // 0x8
+    field public static final int STATE_PULLING_CALL = 11; // 0xb
     field public static final int STATE_RINGING = 2; // 0x2
     field public static final int STATE_SELECT_PHONE_ACCOUNT = 8; // 0x8
   }
@@ -38799,6 +38921,7 @@
     method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
     method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
     method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
+    method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
@@ -38829,6 +38952,7 @@
     method public static java.lang.String propertiesToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 4194304; // 0x400000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 8388608; // 0x800000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
     field public static final int CAPABILITY_HOLD = 1; // 0x1
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
@@ -38848,7 +38972,9 @@
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
     field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
     field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
+    field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
     field public static final int PROPERTY_WIFI = 8; // 0x8
   }
 
@@ -38905,6 +39031,7 @@
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final deprecated long getConnectTimeMillis();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -38919,6 +39046,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
@@ -38927,15 +39055,19 @@
     method public void onStopDtmfTone();
     method public void onSwap();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
     method public final void removeConnection(android.telecom.Connection);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
     method public final void setActive();
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final deprecated void setConnectTimeMillis(long);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setConnectionTime(long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setOnHold();
     method public final void setStatusHints(android.telecom.StatusHints);
     method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -38962,6 +39094,7 @@
     method public final android.telecom.Conference getConference();
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final int getState();
@@ -38973,16 +39106,24 @@
     method public void onAnswer();
     method public deprecated void onAudioStateChanged(android.telecom.AudioState);
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onCallEvent(java.lang.String, android.os.Bundle);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
+    method public void onPullExternalCall();
     method public void onReject();
     method public void onReject(java.lang.String);
     method public void onSeparate();
     method public void onStateChanged(int);
     method public void onStopDtmfTone();
     method public void onUnhold();
+    method public static java.lang.String propertiesToString(int);
+    method public final void putExtras(android.os.Bundle);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
+    method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
     method public final void setAudioModeIsVoip(boolean);
@@ -38990,9 +39131,10 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setInitialized();
     method public final void setInitializing();
     method public final void setNextPostDialChar(char);
@@ -39006,6 +39148,7 @@
     method public static java.lang.String stateToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
     field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
@@ -39023,15 +39166,20 @@
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
     field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
     field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+    field public static final java.lang.String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED";
+    field public static final java.lang.String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
     field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
     field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
     field public static final int STATE_DISCONNECTED = 6; // 0x6
     field public static final int STATE_HOLDING = 5; // 0x5
     field public static final int STATE_INITIALIZING = 0; // 0x0
     field public static final int STATE_NEW = 1; // 0x1
+    field public static final int STATE_PULLING_CALL = 7; // 0x7
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
@@ -39109,7 +39257,9 @@
     method public java.lang.String getReason();
     method public int getTone();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int ANSWERED_ELSEWHERE = 11; // 0xb
     field public static final int BUSY = 7; // 0x7
+    field public static final int CALL_PULLED = 12; // 0xc
     field public static final int CANCELED = 4; // 0x4
     field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa
     field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR;
@@ -39146,6 +39296,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onCallRemoved(android.telecom.Call);
     method public void onCanAddCallChanged(boolean);
+    method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public deprecated void onPhoneCreated(android.telecom.Phone);
     method public deprecated void onPhoneDestroyed(android.telecom.Phone);
     method public void onSilenceRinger();
@@ -39183,14 +39334,16 @@
   }
 
   public class ParcelableCallAnalytics implements android.os.Parcelable {
-    ctor public ParcelableCallAnalytics(long, long, int, boolean, boolean, int, int, boolean, java.lang.String, boolean);
+    ctor public ParcelableCallAnalytics(long, long, int, boolean, boolean, int, int, boolean, java.lang.String, boolean, java.util.List<android.telecom.ParcelableCallAnalytics.AnalyticsEvent>, java.util.List<android.telecom.ParcelableCallAnalytics.EventTiming>);
     ctor public ParcelableCallAnalytics(android.os.Parcel);
+    method public java.util.List<android.telecom.ParcelableCallAnalytics.AnalyticsEvent> analyticsEvents();
     method public int describeContents();
     method public long getCallDurationMillis();
     method public int getCallTechnologies();
     method public int getCallTerminationCode();
     method public int getCallType();
     method public java.lang.String getConnectionService();
+    method public java.util.List<android.telecom.ParcelableCallAnalytics.EventTiming> getEventTimings();
     method public long getStartTimeMillis();
     method public boolean isAdditionalCall();
     method public boolean isCreatedFromExistingConnection();
@@ -39211,6 +39364,73 @@
     field public static final int THIRD_PARTY_PHONE = 16; // 0x10
   }
 
+  public static final class ParcelableCallAnalytics.AnalyticsEvent implements android.os.Parcelable {
+    ctor public ParcelableCallAnalytics.AnalyticsEvent(int, long);
+    method public int describeContents();
+    method public int getEventName();
+    method public long getTimeSinceLastEvent();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int AUDIO_ROUTE_BT = 204; // 0xcc
+    field public static final int AUDIO_ROUTE_EARPIECE = 205; // 0xcd
+    field public static final int AUDIO_ROUTE_HEADSET = 206; // 0xce
+    field public static final int AUDIO_ROUTE_SPEAKER = 207; // 0xcf
+    field public static final int BIND_CS = 5; // 0x5
+    field public static final int BLOCK_CHECK_FINISHED = 105; // 0x69
+    field public static final int BLOCK_CHECK_INITIATED = 104; // 0x68
+    field public static final int CONFERENCE_WITH = 300; // 0x12c
+    field public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics.AnalyticsEvent> CREATOR;
+    field public static final int CS_BOUND = 6; // 0x6
+    field public static final int DIRECT_TO_VM_FINISHED = 103; // 0x67
+    field public static final int DIRECT_TO_VM_INITIATED = 102; // 0x66
+    field public static final int FILTERING_COMPLETED = 107; // 0x6b
+    field public static final int FILTERING_INITIATED = 106; // 0x6a
+    field public static final int FILTERING_TIMED_OUT = 108; // 0x6c
+    field public static final int MUTE = 202; // 0xca
+    field public static final int REMOTELY_HELD = 402; // 0x192
+    field public static final int REMOTELY_UNHELD = 403; // 0x193
+    field public static final int REQUEST_ACCEPT = 7; // 0x7
+    field public static final int REQUEST_HOLD = 400; // 0x190
+    field public static final int REQUEST_PULL = 500; // 0x1f4
+    field public static final int REQUEST_REJECT = 8; // 0x8
+    field public static final int REQUEST_UNHOLD = 401; // 0x191
+    field public static final int SCREENING_COMPLETED = 101; // 0x65
+    field public static final int SCREENING_SENT = 100; // 0x64
+    field public static final int SET_ACTIVE = 1; // 0x1
+    field public static final int SET_DIALING = 4; // 0x4
+    field public static final int SET_DISCONNECTED = 2; // 0x2
+    field public static final int SET_HOLD = 404; // 0x194
+    field public static final int SET_PARENT = 302; // 0x12e
+    field public static final int SET_SELECT_PHONE_ACCOUNT = 0; // 0x0
+    field public static final int SILENCE = 201; // 0xc9
+    field public static final int SKIP_RINGING = 200; // 0xc8
+    field public static final int SPLIT_CONFERENCE = 301; // 0x12d
+    field public static final int START_CONNECTION = 3; // 0x3
+    field public static final int SWAP = 405; // 0x195
+    field public static final int UNMUTE = 203; // 0xcb
+  }
+
+  public static final class ParcelableCallAnalytics.EventTiming implements android.os.Parcelable {
+    ctor public ParcelableCallAnalytics.EventTiming(int, long);
+    method public int describeContents();
+    method public int getName();
+    method public long getTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int ACCEPT_TIMING = 0; // 0x0
+    field public static final int BIND_CS_TIMING = 6; // 0x6
+    field public static final int BLOCK_CHECK_FINISHED_TIMING = 9; // 0x9
+    field public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics.EventTiming> CREATOR;
+    field public static final int DIRECT_TO_VM_FINISHED_TIMING = 8; // 0x8
+    field public static final int DISCONNECT_TIMING = 2; // 0x2
+    field public static final int FILTERING_COMPLETED_TIMING = 10; // 0xa
+    field public static final int FILTERING_TIMED_OUT_TIMING = 11; // 0xb
+    field public static final int HOLD_TIMING = 3; // 0x3
+    field public static final int INVALID = 999999; // 0xf423f
+    field public static final int OUTGOING_TIME_TO_DIALING_TIMING = 5; // 0x5
+    field public static final int REJECT_TIMING = 1; // 0x1
+    field public static final int SCREENING_COMPLETED_TIMING = 7; // 0x7
+    field public static final int UNHOLD_TIMING = 4; // 0x4
+  }
+
   public final deprecated class Phone {
     method public final void addListener(android.telecom.Phone.Listener);
     method public final boolean canAddCall();
@@ -39323,6 +39543,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
     method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onDestroyed(android.telecom.RemoteConference);
     method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -39341,6 +39562,7 @@
     method public android.telecom.RemoteConference getConference();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public int getConnectionCapabilities();
+    method public int getConnectionProperties();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public int getState();
@@ -39352,6 +39574,7 @@
     method public boolean isVoipAudioMode();
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
+    method public void pullExternalCall();
     method public void registerCallback(android.telecom.RemoteConnection.Callback);
     method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
     method public void reject();
@@ -39369,6 +39592,8 @@
     method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference);
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
+    method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
     method public void onDestroyed(android.telecom.RemoteConnection);
     method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
     method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -39422,6 +39647,41 @@
     field public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR;
   }
 
+  public final class TelecomAnalytics implements android.os.Parcelable {
+    ctor public TelecomAnalytics(java.util.List<android.telecom.TelecomAnalytics.SessionTiming>, java.util.List<android.telecom.ParcelableCallAnalytics>);
+    method public int describeContents();
+    method public java.util.List<android.telecom.ParcelableCallAnalytics> getCallAnalytics();
+    method public java.util.List<android.telecom.TelecomAnalytics.SessionTiming> getSessionTimings();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telecom.TelecomAnalytics> CREATOR;
+  }
+
+  public static final class TelecomAnalytics.SessionTiming implements android.os.Parcelable {
+    ctor public TelecomAnalytics.SessionTiming(int, long);
+    method public int describeContents();
+    method public java.lang.Integer getKey();
+    method public long getTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telecom.TelecomAnalytics.SessionTiming> CREATOR;
+    field public static final int CSW_ADD_CONFERENCE_CALL = 108; // 0x6c
+    field public static final int CSW_HANDLE_CREATE_CONNECTION_COMPLETE = 100; // 0x64
+    field public static final int CSW_REMOVE_CALL = 106; // 0x6a
+    field public static final int CSW_SET_ACTIVE = 101; // 0x65
+    field public static final int CSW_SET_DIALING = 103; // 0x67
+    field public static final int CSW_SET_DISCONNECTED = 104; // 0x68
+    field public static final int CSW_SET_IS_CONFERENCED = 107; // 0x6b
+    field public static final int CSW_SET_ON_HOLD = 105; // 0x69
+    field public static final int CSW_SET_RINGING = 102; // 0x66
+    field public static final int ICA_ANSWER_CALL = 1; // 0x1
+    field public static final int ICA_CONFERENCE = 8; // 0x8
+    field public static final int ICA_DISCONNECT_CALL = 3; // 0x3
+    field public static final int ICA_HOLD_CALL = 4; // 0x4
+    field public static final int ICA_MUTE = 6; // 0x6
+    field public static final int ICA_REJECT_CALL = 2; // 0x2
+    field public static final int ICA_SET_AUDIO_ROUTE = 7; // 0x7
+    field public static final int ICA_UNHOLD_CALL = 5; // 0x5
+  }
+
   public class TelecomManager {
     method public void acceptRingingCall();
     method public void acceptRingingCall(int);
@@ -39431,7 +39691,7 @@
     method public deprecated void clearAccounts();
     method public void clearPhoneAccounts();
     method public android.content.Intent createManageBlockedNumbersIntent();
-    method public java.util.List<android.telecom.ParcelableCallAnalytics> dumpAnalytics();
+    method public android.telecom.TelecomAnalytics dumpAnalytics();
     method public void enablePhoneAccount(android.telecom.PhoneAccountHandle, boolean);
     method public boolean endCall();
     method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -39492,6 +39752,7 @@
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
     field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
+    field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -40134,6 +40395,26 @@
     method public void onSubscriptionsChanged();
   }
 
+  public final class TelephonyHistogram implements android.os.Parcelable {
+    ctor public TelephonyHistogram(int, int, int);
+    ctor public TelephonyHistogram(android.telephony.TelephonyHistogram);
+    ctor public TelephonyHistogram(android.os.Parcel);
+    method public void addTimeTaken(int);
+    method public int describeContents();
+    method public int getAverageTime();
+    method public int getBucketCount();
+    method public int[] getBucketCounters();
+    method public int[] getBucketEndPoints();
+    method public int getCategory();
+    method public int getId();
+    method public int getMaxTime();
+    method public int getMinTime();
+    method public int getSampleCount();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.TelephonyHistogram> CREATOR;
+    field public static final int TELEPHONY_CATEGORY_RIL = 1; // 0x1
+  }
+
   public class TelephonyManager {
     method public void answerRingingCall();
     method public void call(java.lang.String, java.lang.String);
@@ -40183,6 +40464,7 @@
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
     method public java.lang.String getSubscriberId();
+    method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public int getVoiceNetworkType();
@@ -43094,7 +43376,10 @@
     method public boolean equals(android.util.DisplayMetrics);
     method public void setTo(android.util.DisplayMetrics);
     method public void setToDefaults();
+    field public static final int DENSITY_260 = 260; // 0x104
     field public static final int DENSITY_280 = 280; // 0x118
+    field public static final int DENSITY_300 = 300; // 0x12c
+    field public static final int DENSITY_340 = 340; // 0x154
     field public static final int DENSITY_360 = 360; // 0x168
     field public static final int DENSITY_400 = 400; // 0x190
     field public static final int DENSITY_420 = 420; // 0x1a4
@@ -44303,6 +44588,10 @@
     field public static final int KEYCODE_FOCUS = 80; // 0x50
     field public static final int KEYCODE_FORWARD = 125; // 0x7d
     field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+    field public static final int KEYCODE_FP_NAV_DOWN = 281; // 0x119
+    field public static final int KEYCODE_FP_NAV_LEFT = 282; // 0x11a
+    field public static final int KEYCODE_FP_NAV_RIGHT = 283; // 0x11b
+    field public static final int KEYCODE_FP_NAV_UP = 280; // 0x118
     field public static final int KEYCODE_FUNCTION = 119; // 0x77
     field public static final int KEYCODE_G = 35; // 0x23
     field public static final int KEYCODE_GRAVE = 68; // 0x44
@@ -47529,6 +47818,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -47623,6 +47913,7 @@
     field public static final int IME_NULL = 0; // 0x0
     field public int actionId;
     field public java.lang.CharSequence actionLabel;
+    field public java.lang.String[] contentMimeTypes;
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
@@ -47694,6 +47985,7 @@
     method public abstract java.lang.CharSequence getSelectedText(int);
     method public abstract java.lang.CharSequence getTextAfterCursor(int, int);
     method public abstract java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public abstract boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public abstract boolean performContextMenuAction(int);
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -47727,6 +48019,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -47739,6 +48032,17 @@
     method public void setTarget(android.view.inputmethod.InputConnection);
   }
 
+  public class InputContentInfo implements android.os.Parcelable {
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
+    method public int describeContents();
+    method public android.net.Uri getContentUri();
+    method public android.content.ClipDescription getDescription();
+    method public android.net.Uri getLinkUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
+  }
+
   public abstract interface InputMethod {
     method public abstract void attachToken(android.os.IBinder);
     method public abstract void bindInput(android.view.inputmethod.InputBinding);
diff --git a/api/test-current.txt b/api/test-current.txt
index 57d6f66..dcdcf9d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -420,7 +420,9 @@
     field public static final int contentInsetStart = 16843859; // 0x1010453
     field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
     field public static final int contextClickable = 16844007; // 0x10104e7
+    field public static final int contextDescription = 16844082; // 0x1010532
     field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
+    field public static final int contextUri = 16844081; // 0x1010531
     field public static final int controlX1 = 16843772; // 0x10103fc
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
@@ -1039,6 +1041,7 @@
     field public static final int rotation = 16843558; // 0x1010326
     field public static final int rotationX = 16843559; // 0x1010327
     field public static final int rotationY = 16843560; // 0x1010328
+    field public static final int roundIcon = 16844080; // 0x1010530
     field public static final int rowCount = 16843637; // 0x1010375
     field public static final int rowDelay = 16843216; // 0x10101d0
     field public static final int rowEdgeFlags = 16843329; // 0x1010241
@@ -1106,11 +1109,20 @@
     field public static final int shareInterpolator = 16843195; // 0x10101bb
     field public static final int sharedUserId = 16842763; // 0x101000b
     field public static final int sharedUserLabel = 16843361; // 0x1010261
+    field public static final int shortcutCategories = 16844077; // 0x101052d
+    field public static final int shortcutDisabledMessage = 16844076; // 0x101052c
+    field public static final int shortcutIcon = 16844073; // 0x1010529
+    field public static final int shortcutId = 16844072; // 0x1010528
+    field public static final int shortcutIntentAction = 16844078; // 0x101052e
+    field public static final int shortcutIntentData = 16844079; // 0x101052f
+    field public static final int shortcutLongLabel = 16844075; // 0x101052b
+    field public static final int shortcutShortLabel = 16844074; // 0x101052a
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
     field public static final int showDividers = 16843561; // 0x1010329
     field public static final int showForAllUsers = 16844015; // 0x10104ef
+    field public static final int showMetadataInPreview = 16844083; // 0x1010533
     field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
     field public static final int showSilent = 16843259; // 0x10101fb
     field public static final int showText = 16843949; // 0x10104ad
@@ -5038,12 +5050,14 @@
     method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder);
     method public java.lang.CharSequence getCancelLabel();
     method public java.lang.CharSequence getConfirmLabel();
+    method public boolean getHintDisplayActionInline();
     method public boolean getHintLaunchesActivity();
     method public java.lang.CharSequence getInProgressLabel();
     method public boolean isAvailableOffline();
     method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean);
     method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
     method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+    method public android.app.Notification.Action.WearableExtender setHintDisplayActionInline(boolean);
     method public android.app.Notification.Action.WearableExtender setHintLaunchesActivity(boolean);
     method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
   }
@@ -5762,7 +5776,10 @@
     method public android.content.pm.ServiceInfo getServiceInfo();
     method public java.lang.String getServiceName();
     method public java.lang.String getSettingsActivity();
+    method public boolean getShowMetadataInPreview();
     method public java.lang.CharSequence loadAuthor(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
+    method public java.lang.CharSequence loadContextDescription(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
+    method public android.net.Uri loadContextUri(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
     method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager) throws android.content.res.Resources.NotFoundException;
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
@@ -6490,11 +6507,13 @@
     method public android.content.res.Configuration getConfiguration();
     method public int getEventType();
     method public java.lang.String getPackageName();
+    method public java.lang.String getShortcutId();
     method public long getTimeStamp();
     field public static final int CONFIGURATION_CHANGE = 5; // 0x5
     field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
     field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
     field public static final int NONE = 0; // 0x0
+    field public static final int SHORTCUT_INVOCATION = 8; // 0x8
     field public static final int USER_INTERACTION = 7; // 0x7
   }
 
@@ -8199,6 +8218,7 @@
     field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
     field public static final java.lang.String SEARCH_SERVICE = "search";
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
+    field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
     field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
     field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9512,13 +9532,22 @@
   public class LauncherApps {
     ctor public LauncherApps(android.content.Context);
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+    method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
+    method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
+    method public boolean hasShortcutHostPermission();
     method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
     method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
+    method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
     method public void registerCallback(android.content.pm.LauncherApps.Callback);
     method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
     method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
     method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+    method public boolean startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
+    method public boolean startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
     method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
   }
 
@@ -9531,6 +9560,20 @@
     method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
     method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
     method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
+    method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle);
+  }
+
+  public static class LauncherApps.ShortcutQuery {
+    ctor public LauncherApps.ShortcutQuery();
+    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
   }
 
   public class PackageInfo implements android.os.Parcelable {
@@ -10032,6 +10075,75 @@
     field public java.lang.String permission;
   }
 
+  public final class ShortcutInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.content.ComponentName getActivity();
+    method public java.util.Set<java.lang.String> getCategories();
+    method public java.lang.CharSequence getDisabledMessage();
+    method public int getDisabledMessageResourceId();
+    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.CharSequence getLongLabel();
+    method public int getLongLabelResourceId();
+    method public java.lang.String getPackage();
+    method public int getRank();
+    method public java.lang.CharSequence getShortLabel();
+    method public int getShortLabelResourceId();
+    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 isDynamic();
+    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 android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
+  }
+
+  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 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 setExtras(android.os.PersistableBundle);
+    method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
+    method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+    method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setRank(int);
+    method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String);
+  }
+
+  public class ShortcutManager {
+    ctor public ShortcutManager(android.content.Context);
+    method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+    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 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();
+    method public void removeAllDynamicShortcuts();
+    method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
+    method public void reportShortcutUsed(java.lang.String);
+    method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+    method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+  }
+
   public class Signature implements android.os.Parcelable {
     ctor public Signature(byte[]);
     ctor public Signature(java.lang.String);
@@ -30713,6 +30825,7 @@
   public static class CallLog.Calls implements android.provider.BaseColumns {
     ctor public CallLog.Calls();
     method public static java.lang.String getLastOutgoingCall(android.content.Context);
+    field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
     field public static final int BLOCKED_TYPE = 6; // 0x6
     field public static final java.lang.String CACHED_FORMATTED_NUMBER = "formatted_number";
     field public static final java.lang.String CACHED_LOOKUP_URI = "lookup_uri";
@@ -30735,6 +30848,7 @@
     field public static final java.lang.String DURATION = "duration";
     field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER";
     field public static final java.lang.String FEATURES = "features";
+    field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
     field public static final int FEATURES_VIDEO = 1; // 0x1
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
@@ -32354,6 +32468,7 @@
     field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
+    field public static final java.lang.String ACTION_DELETION_HELPER_SETTINGS = "android.settings.DELETION_HELPER_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
@@ -33060,6 +33175,7 @@
     field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff
     field public static final java.lang.String SETTINGS_URI = "settings_uri";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
+    field public static final java.lang.String SOURCE_TYPE = "source_type";
     field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri";
   }
 
@@ -36013,9 +36129,14 @@
     method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
+    method public void pullExternalCall();
+    method public final void putExtras(android.os.Bundle);
     method public void registerCallback(android.telecom.Call.Callback);
     method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
+    method public void sendCallEvent(java.lang.String, android.os.Bundle);
     method public void splitFromConference();
     method public void stopDtmfTone();
     method public void swapConference();
@@ -36029,6 +36150,7 @@
     field public static final int STATE_DISCONNECTING = 10; // 0xa
     field public static final int STATE_HOLDING = 3; // 0x3
     field public static final int STATE_NEW = 0; // 0x0
+    field public static final int STATE_PULLING_CALL = 11; // 0xb
     field public static final int STATE_RINGING = 2; // 0x2
     field public static final int STATE_SELECT_PHONE_ACCOUNT = 8; // 0x8
   }
@@ -36039,6 +36161,7 @@
     method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
     method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
     method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
+    method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
@@ -36069,6 +36192,7 @@
     method public static java.lang.String propertiesToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 4194304; // 0x400000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 8388608; // 0x800000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
     field public static final int CAPABILITY_HOLD = 1; // 0x1
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
@@ -36088,7 +36212,9 @@
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
     field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
     field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
+    field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
     field public static final int PROPERTY_WIFI = 8; // 0x8
   }
 
@@ -36139,6 +36265,7 @@
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -36151,6 +36278,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
@@ -36159,14 +36287,18 @@
     method public void onStopDtmfTone();
     method public void onSwap();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
     method public final void removeConnection(android.telecom.Connection);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
     method public final void setActive();
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setConnectionTime(long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setOnHold();
     method public final void setStatusHints(android.telecom.StatusHints);
     method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -36192,6 +36324,7 @@
     method public final android.telecom.Conference getConference();
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final int getState();
@@ -36202,16 +36335,24 @@
     method public void onAnswer(int);
     method public void onAnswer();
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onCallEvent(java.lang.String, android.os.Bundle);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
+    method public void onPullExternalCall();
     method public void onReject();
     method public void onReject(java.lang.String);
     method public void onSeparate();
     method public void onStateChanged(int);
     method public void onStopDtmfTone();
     method public void onUnhold();
+    method public static java.lang.String propertiesToString(int);
+    method public final void putExtras(android.os.Bundle);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public final void removeExtras(java.lang.String...);
+    method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
     method public final void setAudioModeIsVoip(boolean);
@@ -36219,9 +36360,10 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setInitialized();
     method public final void setInitializing();
     method public final void setNextPostDialChar(char);
@@ -36235,6 +36377,7 @@
     method public static java.lang.String stateToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
     field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
@@ -36252,15 +36395,20 @@
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
     field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
     field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+    field public static final java.lang.String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED";
+    field public static final java.lang.String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
     field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
     field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
     field public static final int STATE_DISCONNECTED = 6; // 0x6
     field public static final int STATE_HOLDING = 5; // 0x5
     field public static final int STATE_INITIALIZING = 0; // 0x0
     field public static final int STATE_NEW = 1; // 0x1
+    field public static final int STATE_PULLING_CALL = 7; // 0x7
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
@@ -36338,7 +36486,9 @@
     method public java.lang.String getReason();
     method public int getTone();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int ANSWERED_ELSEWHERE = 11; // 0xb
     field public static final int BUSY = 7; // 0x7
+    field public static final int CALL_PULLED = 12; // 0xc
     field public static final int CANCELED = 4; // 0x4
     field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa
     field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR;
@@ -36374,6 +36524,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onCallRemoved(android.telecom.Call);
     method public void onCanAddCallChanged(boolean);
+    method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onSilenceRinger();
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
@@ -36496,6 +36647,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
     method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onDestroyed(android.telecom.RemoteConference);
     method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -36514,6 +36666,7 @@
     method public android.telecom.RemoteConference getConference();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public int getConnectionCapabilities();
+    method public int getConnectionProperties();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public int getState();
@@ -36525,6 +36678,7 @@
     method public boolean isVoipAudioMode();
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
+    method public void pullExternalCall();
     method public void registerCallback(android.telecom.RemoteConnection.Callback);
     method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
     method public void reject();
@@ -36541,6 +36695,8 @@
     method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference);
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
+    method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
     method public void onDestroyed(android.telecom.RemoteConnection);
     method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
     method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -36637,6 +36793,7 @@
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
     field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
+    field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -37277,6 +37434,26 @@
     method public void onSubscriptionsChanged();
   }
 
+  public final class TelephonyHistogram implements android.os.Parcelable {
+    ctor public TelephonyHistogram(int, int, int);
+    ctor public TelephonyHistogram(android.telephony.TelephonyHistogram);
+    ctor public TelephonyHistogram(android.os.Parcel);
+    method public void addTimeTaken(int);
+    method public int describeContents();
+    method public int getAverageTime();
+    method public int getBucketCount();
+    method public int[] getBucketCounters();
+    method public int[] getBucketEndPoints();
+    method public int getCategory();
+    method public int getId();
+    method public int getMaxTime();
+    method public int getMinTime();
+    method public int getSampleCount();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.TelephonyHistogram> CREATOR;
+    field public static final int TELEPHONY_CATEGORY_RIL = 1; // 0x1
+  }
+
   public class TelephonyManager {
     method public boolean canChangeDtmfToneLength();
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
@@ -37307,6 +37484,7 @@
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
     method public java.lang.String getSubscriberId();
+    method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public int getVoiceNetworkType();
@@ -40174,7 +40352,10 @@
     method public boolean equals(android.util.DisplayMetrics);
     method public void setTo(android.util.DisplayMetrics);
     method public void setToDefaults();
+    field public static final int DENSITY_260 = 260; // 0x104
     field public static final int DENSITY_280 = 280; // 0x118
+    field public static final int DENSITY_300 = 300; // 0x12c
+    field public static final int DENSITY_340 = 340; // 0x154
     field public static final int DENSITY_360 = 360; // 0x168
     field public static final int DENSITY_400 = 400; // 0x190
     field public static final int DENSITY_420 = 420; // 0x1a4
@@ -41383,6 +41564,10 @@
     field public static final int KEYCODE_FOCUS = 80; // 0x50
     field public static final int KEYCODE_FORWARD = 125; // 0x7d
     field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+    field public static final int KEYCODE_FP_NAV_DOWN = 281; // 0x119
+    field public static final int KEYCODE_FP_NAV_LEFT = 282; // 0x11a
+    field public static final int KEYCODE_FP_NAV_RIGHT = 283; // 0x11b
+    field public static final int KEYCODE_FP_NAV_UP = 280; // 0x118
     field public static final int KEYCODE_FUNCTION = 119; // 0x77
     field public static final int KEYCODE_G = 35; // 0x23
     field public static final int KEYCODE_GRAVE = 68; // 0x44
@@ -44606,6 +44791,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44700,6 +44886,7 @@
     field public static final int IME_NULL = 0; // 0x0
     field public int actionId;
     field public java.lang.CharSequence actionLabel;
+    field public java.lang.String[] contentMimeTypes;
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
@@ -44771,6 +44958,7 @@
     method public abstract java.lang.CharSequence getSelectedText(int);
     method public abstract java.lang.CharSequence getTextAfterCursor(int, int);
     method public abstract java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public abstract boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public abstract boolean performContextMenuAction(int);
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44804,6 +44992,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44816,6 +45005,17 @@
     method public void setTarget(android.view.inputmethod.InputConnection);
   }
 
+  public class InputContentInfo implements android.os.Parcelable {
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
+    method public int describeContents();
+    method public android.net.Uri getContentUri();
+    method public android.content.ClipDescription getDescription();
+    method public android.net.Uri getLinkUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
+  }
+
   public abstract interface InputMethod {
     method public abstract void attachToken(android.os.IBinder);
     method public abstract void bindInput(android.view.inputmethod.InputBinding);
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c597ed2..e8fcd3b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -18,6 +18,9 @@
 #define LOG_TAG "BootAnimation"
 
 #include <stdint.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <math.h>
 #include <fcntl.h>
@@ -57,23 +60,29 @@
 #include "BootAnimation.h"
 #include "AudioPlayer.h"
 
-#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
-#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
-#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"
-#define EXIT_PROP_NAME "service.bootanim.exit"
-
 namespace android {
 
+static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
+static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
+static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
+static const char SYSTEM_DATA_DIR_PATH[] = "/data/system";
+static const char SYSTEM_TIME_DIR_NAME[] = "time";
+static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time";
+static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change";
+static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change";
+static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate";
+static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate";
+static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
 static const int ANIM_ENTRY_NAME_MAX = 256;
 
 // ---------------------------------------------------------------------------
 
-BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true) {
+BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
+        mTimeCheckThread(NULL) {
     mSession = new SurfaceComposerClient();
 }
 
-BootAnimation::~BootAnimation() {
-}
+BootAnimation::~BootAnimation() {}
 
 void BootAnimation::onFirstRef() {
     status_t err = mSession->linkToComposerDeath(this);
@@ -638,11 +647,21 @@
 
 bool BootAnimation::movie()
 {
-
     Animation* animation = loadAnimation(mZipFileName);
     if (animation == NULL)
         return false;
 
+    bool anyPartHasClock = false;
+    for (size_t i=0; i < animation->parts.size(); i++) {
+        if(animation->parts[i].clockPosY >= 0) {
+            anyPartHasClock = true;
+            break;
+        }
+    }
+    if (!anyPartHasClock) {
+        mClockEnabled = false;
+    }
+
     // Blend required to draw time on top of animation frames.
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     glShadeModel(GL_FLAT);
@@ -664,7 +683,18 @@
         mClockEnabled = clockTextureInitialized;
     }
 
+    if (mClockEnabled && !updateIsTimeAccurate()) {
+        mTimeCheckThread = new TimeCheckThread(this);
+        mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
+    }
+
     playAnimation(*animation);
+
+    if (mTimeCheckThread != NULL) {
+        mTimeCheckThread->requestExit();
+        mTimeCheckThread = NULL;
+    }
+
     releaseAnimation(animation);
 
     if (clockTextureInitialized) {
@@ -745,7 +775,7 @@
                 // which is equivalent to mHeight - (yc + animation.height)
                 glDrawTexiOES(xc, mHeight - (yc + animation.height),
                               0, animation.width, animation.height);
-                if (mClockEnabled && part.clockPosY >= 0) {
+                if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) {
                     drawTime(mClock, part.clockPosY);
                 }
 
@@ -824,6 +854,132 @@
     mLoadedFiles.remove(fn);
     return animation;
 }
+
+bool BootAnimation::updateIsTimeAccurate() {
+    static constexpr long long MAX_TIME_IN_PAST =   60000LL * 60LL * 24LL * 30LL;  // 30 days
+    static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL;  // 90 minutes
+
+    if (mTimeIsAccurate) {
+        return true;
+    }
+
+    struct stat statResult;
+    if(stat(ACCURATE_TIME_FLAG_FILE_PATH, &statResult) == 0) {
+        mTimeIsAccurate = true;
+        return true;
+    }
+
+    FILE* file = fopen(LAST_TIME_CHANGED_FILE_PATH, "r");
+    if (file != NULL) {
+      long long lastChangedTime = 0;
+      fscanf(file, "%lld", &lastChangedTime);
+      fclose(file);
+      if (lastChangedTime > 0) {
+        struct timespec now;
+        clock_gettime(CLOCK_REALTIME, &now);
+        // Match the Java timestamp format
+        long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL);
+        if (lastChangedTime > rtcNow - MAX_TIME_IN_PAST
+            && lastChangedTime < rtcNow + MAX_TIME_IN_FUTURE) {
+            mTimeIsAccurate = true;
+        }
+      }
+    }
+
+    return mTimeIsAccurate;
+}
+
+BootAnimation::TimeCheckThread::TimeCheckThread(BootAnimation* bootAnimation) : Thread(false),
+    mInotifyFd(-1), mSystemWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {}
+
+BootAnimation::TimeCheckThread::~TimeCheckThread() {
+    // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD.
+    close(mInotifyFd);
+}
+
+bool BootAnimation::TimeCheckThread::threadLoop() {
+    bool shouldLoop = doThreadLoop() && !mBootAnimation->mTimeIsAccurate
+        && mBootAnimation->mClockEnabled;
+    if (!shouldLoop) {
+        close(mInotifyFd);
+        mInotifyFd = -1;
+    }
+    return shouldLoop;
+}
+
+bool BootAnimation::TimeCheckThread::doThreadLoop() {
+    static constexpr int BUFF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1));
+
+    // Poll instead of doing a blocking read so the Thread can exit if requested.
+    struct pollfd pfd = { mInotifyFd, POLLIN, 0 };
+    ssize_t pollResult = poll(&pfd, 1, 1000);
+
+    if (pollResult == 0) {
+        return true;
+    } else if (pollResult < 0) {
+        ALOGE("Could not poll inotify events");
+        return false;
+    }
+
+    char buff[BUFF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));;
+    ssize_t length = read(mInotifyFd, buff, BUFF_LEN);
+    if (length == 0) {
+        return true;
+    } else if (length < 0) {
+        ALOGE("Could not read inotify events");
+        return false;
+    }
+
+    const struct inotify_event *event;
+    for (char* ptr = buff; ptr < buff + length; ptr += sizeof(struct inotify_event) + event->len) {
+        event = (const struct inotify_event *) ptr;
+        if (event->wd == mSystemWd && strcmp(SYSTEM_TIME_DIR_NAME, event->name) == 0) {
+            addTimeDirWatch();
+        } else if (event->wd == mTimeWd && (strcmp(LAST_TIME_CHANGED_FILE_NAME, event->name) == 0
+                || strcmp(ACCURATE_TIME_FLAG_FILE_NAME, event->name) == 0)) {
+            return !mBootAnimation->updateIsTimeAccurate();
+        }
+    }
+
+    return true;
+}
+
+void BootAnimation::TimeCheckThread::addTimeDirWatch() {
+        mTimeWd = inotify_add_watch(mInotifyFd, SYSTEM_TIME_DIR_PATH,
+                IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB);
+        if (mTimeWd > 0) {
+            // No need to watch for the time directory to be created if it already exists
+            inotify_rm_watch(mInotifyFd, mSystemWd);
+            mSystemWd = -1;
+        }
+}
+
+status_t BootAnimation::TimeCheckThread::readyToRun() {
+    mInotifyFd = inotify_init();
+    if (mInotifyFd < 0) {
+        ALOGE("Could not initialize inotify fd");
+        return NO_INIT;
+    }
+
+    mSystemWd = inotify_add_watch(mInotifyFd, SYSTEM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB);
+    if (mSystemWd < 0) {
+        close(mInotifyFd);
+        mInotifyFd = -1;
+        ALOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH);
+        return NO_INIT;
+    }
+
+    addTimeDirWatch();
+
+    if (mBootAnimation->updateIsTimeAccurate()) {
+        close(mInotifyFd);
+        mInotifyFd = -1;
+        return ALREADY_EXISTS;
+    }
+
+    return NO_ERROR;
+}
+
 // ---------------------------------------------------------------------------
 
 }
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index d49e1ec..1c3d53a 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -51,6 +51,24 @@
     virtual void        onFirstRef();
     virtual void        binderDied(const wp<IBinder>& who);
 
+    bool                updateIsTimeAccurate();
+
+    class TimeCheckThread : public Thread {
+    public:
+        TimeCheckThread(BootAnimation* bootAnimation);
+        virtual ~TimeCheckThread();
+    private:
+        virtual status_t    readyToRun();
+        virtual bool        threadLoop();
+        bool                doThreadLoop();
+        void                addTimeDirWatch();
+
+        int mInotifyFd;
+        int mSystemWd;
+        int mTimeWd;
+        BootAnimation* mBootAnimation;
+    };
+
     struct Texture {
         GLint   w;
         GLint   h;
@@ -113,8 +131,10 @@
     sp<SurfaceControl> mFlingerSurfaceControl;
     sp<Surface> mFlingerSurface;
     bool        mClockEnabled;
+    bool        mTimeIsAccurate;
     String8     mZipFileName;
     SortedVector<String8> mLoadedFiles;
+    sp<TimeCheckThread> mTimeCheckThread;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 224823e..ba16e67 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -1095,8 +1095,12 @@
         }
         // TODO: We need a better way to get data out of keyframes.
         if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase
-                || mKeyframes instanceof PathKeyframes.IntKeyframesBase) {
-            // property values will animate based on external data source (e.g. Path)
+                || mKeyframes instanceof PathKeyframes.IntKeyframesBase
+                || (mKeyframes.getKeyframes() != null && mKeyframes.getKeyframes().size() > 2)) {
+            // When a pvh has more than 2 keyframes, that means there are intermediate values in
+            // addition to start/end values defined for animators. Another case where such
+            // intermediate values are defined is when animator has a path to animate along. In
+            // these cases, a data source is needed to capture these intermediate values.
             values.dataSource = new PropertyValues.DataSource() {
                 @Override
                 public Object getValueAtFraction(float fraction) {
@@ -1108,6 +1112,13 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public Class getValueType() {
+        return mValueType;
+    }
+
     @Override
     public String toString() {
         return mPropertyName + ": " + mKeyframes.toString();
@@ -1178,6 +1189,15 @@
         }
 
         @Override
+        public void setProperty(Property property) {
+            if (property instanceof IntProperty) {
+                mIntProperty = (IntProperty) property;
+            } else {
+                super.setProperty(property);
+            }
+        }
+
+        @Override
         public void setIntValues(int... values) {
             super.setIntValues(values);
             mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
@@ -1316,6 +1336,15 @@
         }
 
         @Override
+        public void setProperty(Property property) {
+            if (property instanceof FloatProperty) {
+                mFloatProperty = (FloatProperty) property;
+            } else {
+                super.setProperty(property);
+            }
+        }
+
+        @Override
         public void setFloatValues(float... values) {
             super.setFloatValues(values);
             mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
@@ -1516,7 +1545,7 @@
                     }
                     propertyMap.put(mPropertyName, mJniSetter);
                 }
-           }
+            }
         }
     }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 13cf46d..ec8b288 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4220,6 +4220,7 @@
     public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
             @Nullable Bundle options) {
         if (mParent == null) {
+            options = transferSpringboardActivityOptions(options);
             Instrumentation.ActivityResult ar =
                 mInstrumentation.execStartActivity(
                     this, mMainThread.getApplicationThread(), mToken, this,
@@ -4268,6 +4269,17 @@
         }
     }
 
+    private Bundle transferSpringboardActivityOptions(Bundle options) {
+        if (options == null && (mWindow != null && !mWindow.isActive())) {
+            final ActivityOptions activityOptions = getActivityOptions();
+            if (activityOptions != null &&
+                    activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+                return activityOptions.toBundle();
+            }
+        }
+        return options;
+    }
+
     /**
      * @hide Implement to provide correct calling token.
      */
@@ -4283,6 +4295,7 @@
         if (mParent != null) {
             throw new RuntimeException("Can't be called from a child");
         }
+        options = transferSpringboardActivityOptions(options);
         Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
                 this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode,
                 options, user);
@@ -4318,6 +4331,7 @@
         if (mParent != null) {
             throw new RuntimeException("Can't be called from a child");
         }
+        options = transferSpringboardActivityOptions(options);
         Instrumentation.ActivityResult ar =
                 mInstrumentation.execStartActivity(
                         this, mMainThread.getApplicationThread(), mToken, this,
@@ -4350,6 +4364,7 @@
         if (mParent != null) {
             throw new RuntimeException("Can't be called from a child");
         }
+        options = transferSpringboardActivityOptions(options);
         Instrumentation.ActivityResult ar =
                 mInstrumentation.execStartActivityAsCaller(
                         this, mMainThread.getApplicationThread(), mToken, this,
@@ -4789,6 +4804,7 @@
      */
     public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
             int requestCode, @Nullable Bundle options) {
+        options = transferSpringboardActivityOptions(options);
         Instrumentation.ActivityResult ar =
             mInstrumentation.execStartActivity(
                 this, mMainThread.getApplicationThread(), mToken, child,
@@ -4854,6 +4870,7 @@
         if (referrer != null) {
             intent.putExtra(Intent.EXTRA_REFERRER, referrer);
         }
+        options = transferSpringboardActivityOptions(options);
         Instrumentation.ActivityResult ar =
             mInstrumentation.execStartActivity(
                 this, mMainThread.getApplicationThread(), mToken, who,
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 4c8ddc7..ccc37d7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -31,10 +31,13 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.transition.TransitionManager;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.AppTransitionAnimationSpec;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.Window;
 
 import java.util.ArrayList;
@@ -640,10 +643,71 @@
     public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
             Pair<View, String>... sharedElements) {
         ActivityOptions opts = new ActivityOptions();
-        if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
-            opts.mAnimationType = ANIM_DEFAULT;
+        makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
+                activity.mExitTransitionListener, sharedElements);
+        return opts;
+    }
+
+    /**
+     * Call this immediately prior to startActivity to begin a shared element transition
+     * from a non-Activity. The window must support Window.FEATURE_ACTIVITY_TRANSITIONS.
+     * The exit transition will start immediately and the shared element transition will
+     * start once the launched Activity's shared element is ready.
+     * <p>
+     * When all transitions have completed and the shared element has been transfered,
+     * the window's decor View will have its visibility set to View.GONE.
+     *
+     * @hide
+     */
+    @SafeVarargs
+    public static ActivityOptions startSharedElementAnimation(Window window,
+            Pair<View, String>... sharedElements) {
+        ActivityOptions opts = new ActivityOptions();
+        final View decorView = window.getDecorView();
+        if (decorView == null) {
             return opts;
         }
+        final ExitTransitionCoordinator exit =
+                makeSceneTransitionAnimation(null, window, opts, null, sharedElements);
+        if (exit != null) {
+            HideWindowListener listener = new HideWindowListener(window, exit);
+            exit.setHideSharedElementsCallback(listener);
+            exit.startExit();
+        }
+        return opts;
+    }
+
+    /**
+     * This method should be called when the {@link #startSharedElementAnimation(Window, Pair[])}
+     * animation must be stopped and the Views reset. This can happen if there was an error
+     * from startActivity or a springboard activity and the animation should stop and reset.
+     *
+     * @hide
+     */
+    public static void stopSharedElementAnimation(Window window) {
+        final View decorView = window.getDecorView();
+        if (decorView == null) {
+            return;
+        }
+        final ExitTransitionCoordinator exit = (ExitTransitionCoordinator)
+                decorView.getTag(com.android.internal.R.id.cross_task_transition);
+        if (exit != null) {
+            exit.cancelPendingTransitions();
+            decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, null);
+            TransitionManager.endTransitions((ViewGroup) decorView);
+            exit.resetViews();
+            exit.clearState();
+            decorView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window,
+            ActivityOptions opts, SharedElementCallback callback,
+            Pair<View, String>[] sharedElements) {
+        if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
+            opts.mAnimationType = ANIM_DEFAULT;
+            return null;
+        }
         opts.mAnimationType = ANIM_SCENE_TRANSITION;
 
         ArrayList<String> names = new ArrayList<String>();
@@ -665,18 +729,22 @@
             }
         }
 
-        ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names,
-                views, false);
+        ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
+                callback, names, names, views, false);
         opts.mTransitionReceiver = exit;
         opts.mSharedElementNames = names;
-        opts.mIsReturning = false;
-        opts.mExitCoordinatorIndex =
-                activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
-        return opts;
+        opts.mIsReturning = (activity == null);
+        if (activity == null) {
+            opts.mExitCoordinatorIndex = -1;
+        } else {
+            opts.mExitCoordinatorIndex =
+                    activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
+        }
+        return exit;
     }
 
     /** @hide */
-    public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+    static ActivityOptions makeSceneTransitionAnimation(Activity activity,
             ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
             int resultCode, Intent resultData) {
         ActivityOptions opts = new ActivityOptions();
@@ -900,6 +968,16 @@
         return mIsReturning;
     }
 
+    /**
+     * Returns whether or not the ActivityOptions was created with
+     * {@link #startSharedElementAnimation(Window, Pair[])}.
+     *
+     * @hide
+     */
+    boolean isCrossTask() {
+        return mExitCoordinatorIndex < 0;
+    }
+
     /** @hide */
     public ArrayList<String> getSharedElementNames() {
         return mSharedElementNames;
@@ -1191,4 +1269,65 @@
                 + ", mAnimationType=" + mAnimationType + ", mStartX=" + mStartX + ", mStartY="
                 + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight;
     }
+
+    private static class HideWindowListener extends Transition.TransitionListenerAdapter
+        implements ExitTransitionCoordinator.HideSharedElementsCallback {
+        private final Window mWindow;
+        private final ExitTransitionCoordinator mExit;
+        private final boolean mWaitingForTransition;
+        private boolean mTransitionEnded;
+        private boolean mSharedElementHidden;
+        private ArrayList<View> mSharedElements;
+
+        public HideWindowListener(Window window, ExitTransitionCoordinator exit) {
+            mWindow = window;
+            mExit = exit;
+            mSharedElements = new ArrayList<>(exit.mSharedElements);
+            Transition transition = mWindow.getExitTransition();
+            if (transition != null) {
+                transition.addListener(this);
+                mWaitingForTransition = true;
+            } else {
+                mWaitingForTransition = false;
+            }
+            View decorView = mWindow.getDecorView();
+            if (decorView != null) {
+                if (decorView.getTag(com.android.internal.R.id.cross_task_transition) != null) {
+                    throw new IllegalStateException(
+                            "Cannot start a transition while one is running");
+                }
+                decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, exit);
+            }
+        }
+
+        @Override
+        public void onTransitionEnd(Transition transition) {
+            mTransitionEnded = true;
+            hideWhenDone();
+            transition.removeListener(this);
+        }
+
+        @Override
+        public void hideSharedElements() {
+            mSharedElementHidden = true;
+            hideWhenDone();
+        }
+
+        private void hideWhenDone() {
+            if (mSharedElementHidden && (!mWaitingForTransition || mTransitionEnded)) {
+                mExit.resetViews();
+                int numSharedElements = mSharedElements.size();
+                for (int i = 0; i < numSharedElements; i++) {
+                    View view = mSharedElements.get(i);
+                    view.requestLayout();
+                }
+                View decorView = mWindow.getDecorView();
+                if (decorView != null) {
+                    decorView.setTagInternal(
+                            com.android.internal.R.id.cross_task_transition, null);
+                    decorView.setVisibility(View.GONE);
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 02eb4d3..2219238 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -185,7 +185,12 @@
             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
         }
         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
-                resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning());
+                resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
+                mEnterActivityOptions.isCrossTask());
+        if (mEnterActivityOptions.isCrossTask()) {
+            mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+            mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+        }
 
         if (!mIsEnterPostponed) {
             startEnter();
@@ -275,7 +280,8 @@
     }
 
     private void restoreReenteringViews() {
-        if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning()) {
+        if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
+                !mEnterTransitionCoordinator.isCrossTask()) {
             mEnterTransitionCoordinator.forceViewsToAppear();
             mExitingFrom = null;
             mExitingTo = null;
@@ -302,8 +308,9 @@
                     }
                 }
 
-                mReturnExitCoordinator =
-                        new ExitTransitionCoordinator(activity, mEnteringNames, null, null, true);
+                mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
+                        activity.getWindow(), activity.mEnterTransitionListener, mEnteringNames,
+                        null, null, true);
                 if (enterViewsTransition != null && decor != null) {
                     enterViewsTransition.resume(decor);
                 }
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 7824072..9928512 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -201,7 +201,7 @@
                 createContextThemeWrapper);
 
         mWindow.alwaysReadCloseOnTouchAttr();
-        mAlert = new AlertController(getContext(), this, getWindow());
+        mAlert = AlertController.create(getContext(), this, getWindow());
     }
 
     static int resolveDialogTheme(Context context, int themeResId) {
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 8bf1e9a..5d12b0d 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -59,12 +59,14 @@
     private boolean mIsViewsTransitionStarted;
     private Transition mEnterViewsTransition;
     private OnPreDrawListener mViewsReadyListener;
+    private final boolean mIsCrossTask;
 
     public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
-            ArrayList<String> sharedElementNames, boolean isReturning) {
+            ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
         super(activity.getWindow(), sharedElementNames,
-                getListener(activity, isReturning), isReturning);
+                getListener(activity, isReturning && !isCrossTask), isReturning);
         mActivity = activity;
+        mIsCrossTask = isCrossTask;
         setResultReceiver(resultReceiver);
         prepareEnter();
         Bundle resultReceiverBundle = new Bundle();
@@ -85,6 +87,10 @@
         }
     }
 
+    boolean isCrossTask() {
+        return mIsCrossTask;
+    }
+
     public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames,
             ArrayList<View> localViews) {
         boolean remap = false;
@@ -325,7 +331,9 @@
         if (mActivity == null || decorView == null) {
             return;
         }
-        mActivity.overridePendingTransition(0, 0);
+        if (!isCrossTask()) {
+            mActivity.overridePendingTransition(0, 0);
+        }
         if (!mIsReturning) {
             mWasOpaque = mActivity.convertToTranslucent(null, null);
             Drawable background = decorView.getBackground();
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 0404288..160c285 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -35,6 +35,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.Window;
 
 import java.util.ArrayList;
 
@@ -59,18 +60,20 @@
     private Bundle mExitSharedElementBundle;
     private boolean mIsExitStarted;
     private boolean mSharedElementsHidden;
+    private HideSharedElementsCallback mHideSharedElementsCallback;
 
-    public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
+    public ExitTransitionCoordinator(Activity activity, Window window,
+            SharedElementCallback listener, ArrayList<String> names,
             ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
-        super(activity.getWindow(), names, getListener(activity, isReturning), isReturning);
+        super(window, names, listener, isReturning);
         viewsReady(mapSharedElements(accepted, mapped));
         stripOffscreenViews();
         mIsBackgroundReady = !isReturning;
         mActivity = activity;
     }
 
-    private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
-        return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
+    void setHideSharedElementsCallback(HideSharedElementsCallback callback) {
+        mHideSharedElementsCallback = callback;
     }
 
     @Override
@@ -188,6 +191,9 @@
 
     private void hideSharedElements() {
         moveSharedElementsFromOverlay();
+        if (mHideSharedElementsCallback != null) {
+            mHideSharedElementsCallback.hideSharedElements();
+        }
         if (!mIsHidden) {
             hideViews(mSharedElements);
         }
@@ -207,7 +213,11 @@
             startTransition(new Runnable() {
                 @Override
                 public void run() {
-                    beginTransitions();
+                    if (mActivity != null) {
+                        beginTransitions();
+                    } else {
+                        startExitTransition();
+                    }
                 }
             });
         }
@@ -508,4 +518,8 @@
             return getWindow().getSharedElementExitTransition();
         }
     }
+
+    interface HideSharedElementsCallback {
+        void hideSharedElements();
+    }
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6fc1820..eaea989 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1294,6 +1294,7 @@
             // Flags bitwise-ored to mFlags
             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
+            private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
 
             // Default value for flags integer
             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
@@ -1480,6 +1481,29 @@
             public boolean getHintLaunchesActivity() {
                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
             }
+
+            /**
+             * Set a hint that this Action should be displayed inline.
+             *
+             * @param hintDisplayInline {@code true} if action should be displayed inline, false
+             *        otherwise
+             * @return this object for method chaining
+             */
+            public WearableExtender setHintDisplayActionInline(
+                    boolean hintDisplayInline) {
+                setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
+                return this;
+            }
+
+            /**
+             * Get a hint that this Action should be displayed inline.
+             *
+             * @return {@code true} if the Action should be displayed inline, {@code false} 
+             *         otherwise. The default value is {@code false} if this was never set.
+             */
+            public boolean getHintDisplayActionInline() {
+                return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
+            }
         }
     }
 
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 7db9fa8..84a16cf 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -31,6 +31,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.wallpaper.WallpaperService;
@@ -72,6 +73,10 @@
      */
     final int mDescriptionResource;
 
+    final int mContextUriResource;
+    final int mContextDescriptionResource;
+    final boolean mShowMetadataInPreview;
+
     /**
      * Constructor.
      * 
@@ -89,7 +94,10 @@
         int thumbnailRes = -1;
         int authorRes = -1;
         int descriptionRes = -1;
-        
+        int contextUriRes = -1;
+        int contextDescriptionRes = -1;
+        boolean showMetadataInPreview = false;
+
         XmlResourceParser parser = null;
         try {
             parser = si.loadXmlMetaData(pm, WallpaperService.SERVICE_META_DATA);
@@ -127,6 +135,15 @@
             descriptionRes = sa.getResourceId(
                     com.android.internal.R.styleable.Wallpaper_description,
                     -1);
+            contextUriRes = sa.getResourceId(
+                    com.android.internal.R.styleable.Wallpaper_contextUri,
+                    -1);
+            contextDescriptionRes = sa.getResourceId(
+                    com.android.internal.R.styleable.Wallpaper_contextDescription,
+                    -1);
+            showMetadataInPreview = sa.getBoolean(
+                    com.android.internal.R.styleable.Wallpaper_showMetadataInPreview,
+                    false);
 
             sa.recycle();
         } catch (NameNotFoundException e) {
@@ -140,6 +157,9 @@
         mThumbnailResource = thumbnailRes;
         mAuthorResource = authorRes;
         mDescriptionResource = descriptionRes;
+        mContextUriResource = contextUriRes;
+        mContextDescriptionResource = contextDescriptionRes;
+        mShowMetadataInPreview = showMetadataInPreview;
     }
 
     WallpaperInfo(Parcel source) {
@@ -147,6 +167,9 @@
         mThumbnailResource = source.readInt();
         mAuthorResource = source.readInt();
         mDescriptionResource = source.readInt();
+        mContextUriResource = source.readInt();
+        mContextDescriptionResource = source.readInt();
+        mShowMetadataInPreview = source.readInt() != 0;
         mService = ResolveInfo.CREATOR.createFromParcel(source);
     }
     
@@ -248,7 +271,55 @@
         return pm.getText(packageName, mDescriptionResource,
                 mService.serviceInfo.applicationInfo);
     }
-    
+
+    /**
+     * Returns an URI that specifies a link for further context about this wallpaper.
+     *
+     * @param pm An instance of {@link PackageManager} to retrieve the URI.
+     * @return The URI.
+     */
+    public Uri loadContextUri(PackageManager pm) throws NotFoundException {
+        if (mContextUriResource <= 0) throw new NotFoundException();
+        String packageName = mService.resolvePackageName;
+        ApplicationInfo applicationInfo = null;
+        if (packageName == null) {
+            packageName = mService.serviceInfo.packageName;
+            applicationInfo = mService.serviceInfo.applicationInfo;
+        }
+        String contextUriString = pm.getText(
+                packageName, mContextUriResource, applicationInfo).toString();
+        if (contextUriString == null) {
+            return null;
+        }
+        return Uri.parse(contextUriString);
+    }
+
+    /**
+     * Retrieves a title of the URI that specifies a link for further context about this wallpaper.
+     *
+     * @param pm An instance of {@link PackageManager} to retrieve the title.
+     * @return The title.
+     */
+    public CharSequence loadContextDescription(PackageManager pm) throws NotFoundException {
+        if (mContextDescriptionResource <= 0) throw new NotFoundException();
+        String packageName = mService.resolvePackageName;
+        ApplicationInfo applicationInfo = null;
+        if (packageName == null) {
+            packageName = mService.serviceInfo.packageName;
+            applicationInfo = mService.serviceInfo.applicationInfo;
+        }
+        return pm.getText(packageName, mContextDescriptionResource, applicationInfo).toString();
+    }
+
+    /**
+     * Queries whether any metadata should be shown when previewing the wallpaper.
+     *
+     * @return Whether any metadata should be shown when previewing the wallpaper.
+     */
+    public boolean getShowMetadataInPreview() {
+        return mShowMetadataInPreview;
+    }
+
     /**
      * Return the class name of an activity that provides a settings UI for
      * the wallpaper.  You can launch this activity be starting it with
@@ -287,6 +358,9 @@
         dest.writeInt(mThumbnailResource);
         dest.writeInt(mAuthorResource);
         dest.writeInt(mDescriptionResource);
+        dest.writeInt(mContextUriResource);
+        dest.writeInt(mContextDescriptionResource);
+        dest.writeInt(mShowMetadataInPreview ? 1 : 0);
         mService.writeToParcel(dest, flags);
     }
 
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 24647f38..a0da258 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -79,6 +79,13 @@
         public static final int USER_INTERACTION = 7;
 
         /**
+         * An event type denoting that an action equivalent to a ShortcutInfo is taken by the user.
+         *
+         * @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
+         */
+        public static final int SHORTCUT_INVOCATION = 8;
+
+        /**
          * {@hide}
          */
         public String mPackage;
@@ -105,6 +112,13 @@
         public Configuration mConfiguration;
 
         /**
+         * ID of the shortcut.
+         * Only present for {@link #SHORTCUT_INVOCATION} event types.
+         * {@hide}
+         */
+        public String mShortcutId;
+
+        /**
          * The package name of the source of this event.
          */
         public String getPackageName() {
@@ -145,6 +159,16 @@
         public Configuration getConfiguration() {
             return mConfiguration;
         }
+
+        /**
+         * Returns the ID of a {@link android.content.pm.ShortcutInfo} for this event
+         * if the event is of type {@link #SHORTCUT_INVOCATION}, otherwise it returns null.
+         *
+         * @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
+         */
+        public String getShortcutId() {
+            return mShortcutId;
+        }
     }
 
     // Only used when creating the resulting events. Not used for reading/unparceling.
@@ -276,8 +300,13 @@
         p.writeInt(event.mEventType);
         p.writeLong(event.mTimeStamp);
 
-        if (event.mEventType == Event.CONFIGURATION_CHANGE) {
-            event.mConfiguration.writeToParcel(p, flags);
+        switch (event.mEventType) {
+            case Event.CONFIGURATION_CHANGE:
+                event.mConfiguration.writeToParcel(p, flags);
+                break;
+            case Event.SHORTCUT_INVOCATION:
+                p.writeString(event.mShortcutId);
+                break;
         }
     }
 
@@ -301,11 +330,18 @@
         eventOut.mEventType = p.readInt();
         eventOut.mTimeStamp = p.readLong();
 
-        // Extract the configuration for configuration change events.
-        if (eventOut.mEventType == Event.CONFIGURATION_CHANGE) {
-            eventOut.mConfiguration = Configuration.CREATOR.createFromParcel(p);
-        } else {
-            eventOut.mConfiguration = null;
+        // Fill out the event-dependant fields.
+        eventOut.mConfiguration = null;
+        eventOut.mShortcutId = null;
+
+        switch (eventOut.mEventType) {
+            case Event.CONFIGURATION_CHANGE:
+                // Extract the configuration for configuration change events.
+                eventOut.mConfiguration = Configuration.CREATOR.createFromParcel(p);
+                break;
+            case Event.SHORTCUT_INVOCATION:
+                eventOut.mShortcutId = p.readString();
+                break;
         }
     }
 
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index b6f1567..a6f91fe 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -56,6 +56,17 @@
     public abstract void reportConfigurationChange(Configuration config, int userId);
 
     /**
+     * Reports that an action equivalent to a ShortcutInfo is taken by the user.
+     *
+     * @param packageName The package name of the shortcut publisher
+     * @param shortcutId The ID of the shortcut in question
+     * @param userId The user in which the content provider was accessed.
+     *
+     * @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
+     */
+    public abstract void reportShortcutUsage(String packageName, String shortcutId, int userId);
+
+    /**
      * Reports that a content provider has been accessed by a foreground app.
      * @param name The authority of the content provider
      * @param pkgName The package name of the content provider
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 2d9f4a7..cd14469 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -34,7 +34,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.SparseArray;
 import android.util.TypedValue;
@@ -187,19 +186,28 @@
                 idsToUpdate[i] = mViews.keyAt(i);
             }
         }
-        List<RemoteViews> updatedViews;
-        int[] updatedIds = new int[idsToUpdate.length];
+        List<PendingHostUpdate> updates;
         try {
-            updatedViews = sService.startListening(
-                    mCallbacks, mContextOpPackageName, mHostId, idsToUpdate, updatedIds).getList();
+            updates = sService.startListening(
+                    mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList();
         }
         catch (RemoteException e) {
             throw new RuntimeException("system server dead?", e);
         }
 
-        int N = updatedViews.size();
+        int N = updates.size();
         for (int i = 0; i < N; i++) {
-            updateAppWidgetView(updatedIds[i], updatedViews.get(i));
+            PendingHostUpdate update = updates.get(i);
+            switch (update.type) {
+                case PendingHostUpdate.TYPE_VIEWS_UPDATE:
+                    updateAppWidgetView(update.appWidgetId, update.views);
+                    break;
+                case PendingHostUpdate.TYPE_PROVIDER_CHANGED:
+                    onProviderChanged(update.appWidgetId, update.widgetInfo);
+                    break;
+                case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
+                    viewDataChanged(update.appWidgetId, update.viewId);
+            }
         }
     }
 
diff --git a/core/java/android/appwidget/PendingHostUpdate.java b/core/java/android/appwidget/PendingHostUpdate.java
new file mode 100644
index 0000000..5780319
--- /dev/null
+++ b/core/java/android/appwidget/PendingHostUpdate.java
@@ -0,0 +1,125 @@
+/*
+ * 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 android.appwidget;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.RemoteViews;
+
+/**
+ * @hide
+ */
+public class PendingHostUpdate implements Parcelable {
+
+    static final int TYPE_VIEWS_UPDATE = 0;
+    static final int TYPE_PROVIDER_CHANGED = 1;
+    static final int TYPE_VIEW_DATA_CHANGED = 2;
+
+    final int appWidgetId;
+    final int type;
+    RemoteViews views;
+    AppWidgetProviderInfo widgetInfo;
+    int viewId;
+
+    public static PendingHostUpdate updateAppWidget(int appWidgetId, RemoteViews views) {
+        PendingHostUpdate update = new PendingHostUpdate(appWidgetId, TYPE_VIEWS_UPDATE);
+        update.views = views;
+        return update;
+    }
+
+    public static PendingHostUpdate providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
+        PendingHostUpdate update = new PendingHostUpdate(appWidgetId, TYPE_PROVIDER_CHANGED);
+        update.widgetInfo = info;
+        return update;
+    }
+
+    public static PendingHostUpdate viewDataChanged(int appWidgetId, int viewId) {
+        PendingHostUpdate update = new PendingHostUpdate(appWidgetId, TYPE_VIEW_DATA_CHANGED);
+        update.viewId = viewId;
+        return update;
+    }
+
+    private PendingHostUpdate(int appWidgetId, int type) {
+        this.appWidgetId = appWidgetId;
+        this.type = type;
+    }
+
+    private PendingHostUpdate(Parcel in) {
+        appWidgetId = in.readInt();
+        type = in.readInt();
+
+        switch (type) {
+            case TYPE_VIEWS_UPDATE:
+                if (0 != in.readInt()) {
+                    views = new RemoteViews(in);
+                }
+                break;
+            case TYPE_PROVIDER_CHANGED:
+                if (0 != in.readInt()) {
+                    widgetInfo = new AppWidgetProviderInfo(in);
+                }
+                break;
+            case TYPE_VIEW_DATA_CHANGED:
+                viewId = in.readInt();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(appWidgetId);
+        dest.writeInt(type);
+        switch (type) {
+            case TYPE_VIEWS_UPDATE:
+                writeNullParcelable(views, dest, flags);
+                break;
+            case TYPE_PROVIDER_CHANGED:
+                writeNullParcelable(widgetInfo, dest, flags);
+                break;
+            case TYPE_VIEW_DATA_CHANGED:
+                dest.writeInt(viewId);
+                break;
+        }
+    }
+
+    private void writeNullParcelable(Parcelable p, Parcel dest, int flags) {
+        if (p != null) {
+            dest.writeInt(1);
+            p.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    /**
+     * Parcelable.Creator that instantiates PendingHostUpdate objects
+     */
+    public static final Parcelable.Creator<PendingHostUpdate> CREATOR
+            = new Parcelable.Creator<PendingHostUpdate>() {
+        public PendingHostUpdate createFromParcel(Parcel parcel) {
+            return new PendingHostUpdate(parcel);
+        }
+
+        public PendingHostUpdate[] newArray(int size) {
+            return new PendingHostUpdate[size];
+        }
+    };
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 09a15de..f46a3b3 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -220,6 +220,46 @@
      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
      */
+
+    /**
+     * Intent used to broadcast the headset's indicator status
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     *   <li> {@link #EXTRA_IND_ID} - The Assigned number of headset Indicator which is supported by
+                                        the headset ( as indicated by AT+BIND
+                                        command in the SLC sequence).or whose value
+                                        is changed (indicated by AT+BIEV command)</li>
+     *   <li> {@link #EXTRA_IND_VALUE}- The updated value of headset indicator. </li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     * <p>{@link #EXTRA_IND_ID} is defined by Bluetooth SIG and each of the indicators are
+     * given an assigned number. Below shows the assigned number of Indicator added so far
+     * - Enhanced Safety - 1
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     * @hide
+     */
+    public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
+            "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
+
+    /**
+     * A String extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
+     * intents that contains the UUID of the headset  indicator (as defined by Bluetooth SIG)
+     * that is being sent.
+     * @hide
+     */
+    public static final String EXTRA_HF_INDICATORS_IND_ID =
+            "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
+
+    /**
+     * A int  extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
+     * intents that contains the value of the Headset indicator that is being sent.
+     * @hide
+     */
+    public static final String EXTRA_HF_INDICATORS_IND_VALUE =
+            "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
+
     public static final int STATE_AUDIO_CONNECTED = 12;
 
     private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
@@ -969,6 +1009,29 @@
         return false;
     }
 
+    /**
+     * Send Headset the BIND response from AG to report change in the status of the
+     * HF indicators to the headset
+     *
+     * @param ind_id Assigned Number of the indicator (defined by SIG)
+     * @param ind_status
+     * possible values- false-Indicator is disabled, no value changes shall be sent for this indicator
+     *                  true-Indicator is enabled, value changes may be sent for this indicator
+     * @hide
+     */
+    public void bindResponse(int ind_id, boolean ind_status) {
+        if (mService != null && isEnabled()) {
+            try {
+                mService.bindResponse(ind_id, ind_status);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
     private final IBluetoothProfileServiceConnection mConnection
             = new IBluetoothProfileServiceConnection.Stub()  {
         @Override
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 0bb4088..6ad442b 100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -59,4 +59,5 @@
                       String number, int type);
     boolean enableWBS();
     boolean disableWBS();
+    void bindResponse(int ind_id, boolean ind_status);
 }
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 0b81ee8..2b853a3 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -37,6 +37,7 @@
     boolean enable();
     boolean enableNoAutoConnect();
     boolean disable(boolean persist);
+    int getState();
     IBluetoothGatt getBluetoothGatt();
 
     boolean bindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0a21939..4cfd76c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3619,8 +3619,6 @@
      *
      * @see #getSystemService
      * @see android.content.pm.ShortcutManager
-     *
-     * @hide
      */
     public static final String SHORTCUT_SERVICE = "shortcut";
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9118f01..df2c6fc 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3174,6 +3174,14 @@
     public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
 
     /**
+     * Boolean intent extra to be used with {@link ACTION_MASTER_CLEAR} in order to force a factory
+     * reset even if {@link android.os.UserManager.DISALLOW_FACTORY_RESET} is set.
+     * @hide
+     */
+    public static final String EXTRA_FORCE_MASTER_CLEAR =
+            "android.intent.extra.FORCE_MASTER_CLEAR";
+
+    /**
      * Broadcast action: report that a settings element is being restored from backup.  The intent
      * contains three extras: EXTRA_SETTING_NAME is a string naming the restored setting,
      * EXTRA_SETTING_NEW_VALUE is the value being restored, and EXTRA_SETTING_PREVIOUS_VALUE
diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java
index afb4c30..2b9cfa0 100644
--- a/core/java/android/content/pm/EphemeralResolveInfo.java
+++ b/core/java/android/content/pm/EphemeralResolveInfo.java
@@ -37,10 +37,7 @@
     /** Algorithm that will be used to generate the domain digest */
     public static final String SHA_ALGORITHM = "SHA-256";
 
-    /** Full digest of the domain hash */
-    private final byte[] mDigestBytes;
-    /** The first 4 bytes of the domain hash */
-    private final int mDigestPrefix;
+    private final EphemeralDigest mDigest;
     private final String mPackageName;
     /** The filters used to match domain */
     private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
@@ -55,29 +52,23 @@
             throw new IllegalArgumentException();
         }
 
-        mDigestBytes = generateDigest(uri);
-        mDigestPrefix =
-                mDigestBytes[0] << 24
-                | mDigestBytes[1] << 16
-                | mDigestBytes[2] << 8
-                | mDigestBytes[3] << 0;
+        mDigest = new EphemeralDigest(uri, -1);
         mFilters.addAll(filters);
         mPackageName = packageName;
     }
 
     EphemeralResolveInfo(Parcel in) {
-        mDigestBytes = in.createByteArray();
-        mDigestPrefix = in.readInt();
+        mDigest = in.readParcelable(null /*loader*/);
         mPackageName = in.readString();
         in.readList(mFilters, null /*loader*/);
     }
 
     public byte[] getDigestBytes() {
-        return mDigestBytes;
+        return mDigest.getDigestBytes()[0];
     }
 
     public int getDigestPrefix() {
-        return mDigestPrefix;
+        return mDigest.getDigestPrefix()[0];
     }
 
     public String getPackageName() {
@@ -88,16 +79,6 @@
         return mFilters;
     }
 
-    private static byte[] generateDigest(Uri uri) {
-        try {
-            final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
-            final byte[] hostBytes = uri.getHost().getBytes();
-            return digest.digest(hostBytes);
-        } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("could not find digest algorithm");
-        }
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -105,8 +86,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeByteArray(mDigestBytes);
-        out.writeInt(mDigestPrefix);
+        out.writeParcelable(mDigest, flags);
         out.writeString(mPackageName);
         out.writeList(mFilters);
     }
@@ -136,4 +116,120 @@
             return mResolveInfo;
         }
     }
+
+    /**
+     * Helper class to generate and store each of the digests and prefixes
+     * sent to the Ephemeral Resolver.
+     * <p>
+     * Since intent filters may want to handle multiple hosts within a
+     * domain [eg “*.google.com”], the resolver is presented with multiple
+     * hash prefixes. For example, "a.b.c.d.e" generates digests for
+     * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e".
+     *
+     * @hide
+     */
+    public static final class EphemeralDigest implements Parcelable {
+        /** Full digest of the domain hashes */
+        private final byte[][] mDigestBytes;
+        /** The first 4 bytes of the domain hashes */
+        private final int[] mDigestPrefix;
+
+        public EphemeralDigest(@NonNull Uri uri, int maxDigests) {
+            if (uri == null) {
+                throw new IllegalArgumentException();
+            }
+            mDigestBytes = generateDigest(uri, maxDigests);
+            mDigestPrefix = new int[mDigestBytes.length];
+            for (int i = 0; i < mDigestBytes.length; i++) {
+                mDigestPrefix[i] =
+                        (mDigestBytes[i][0] & 0xFF) << 24
+                        | (mDigestBytes[i][1] & 0xFF) << 16
+                        | (mDigestBytes[i][2] & 0xFF) << 8
+                        | (mDigestBytes[i][3] & 0xFF) << 0;
+            }
+        }
+
+        private static byte[][] generateDigest(Uri uri, int maxDigests) {
+            ArrayList<byte[]> digests = new ArrayList<>();
+            try {
+                final String host = uri.getHost();
+                final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
+                if (maxDigests <= 0) {
+                    final byte[] hostBytes = host.getBytes();
+                    digests.add(digest.digest(hostBytes));
+                } else {
+                    int prevDot = host.lastIndexOf('.');
+                    prevDot = host.lastIndexOf('.', prevDot - 1);
+                    // shortcut for short URLs
+                    if (prevDot < 0) {
+                        digests.add(digest.digest(host.getBytes()));
+                    } else {
+                        byte[] hostBytes = host.substring(prevDot + 1, host.length()).getBytes();
+                        digests.add(digest.digest(hostBytes));
+                        int digestCount = 1;
+                        while (prevDot >= 0 && digestCount < maxDigests) {
+                            prevDot = host.lastIndexOf('.', prevDot - 1);
+                            hostBytes = host.substring(prevDot + 1, host.length()).getBytes();
+                            digests.add(digest.digest(hostBytes));
+                            digestCount++;
+                        }
+                    }
+                }
+            } catch (NoSuchAlgorithmException e) {
+                throw new IllegalStateException("could not find digest algorithm");
+            }
+            return digests.toArray(new byte[digests.size()][]);
+        }
+
+        EphemeralDigest(Parcel in) {
+            final int digestCount = in.readInt();
+            if (digestCount == -1) {
+                mDigestBytes = null;
+            } else {
+                mDigestBytes = new byte[digestCount][];
+                for (int i = 0; i < digestCount; i++) {
+                    mDigestBytes[i] = in.createByteArray();
+                }
+            }
+            mDigestPrefix = in.createIntArray();
+        }
+
+        public byte[][] getDigestBytes() {
+            return mDigestBytes;
+        }
+
+        public int[] getDigestPrefix() {
+            return mDigestPrefix;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            if (mDigestBytes == null) {
+                out.writeInt(-1);
+            } else {
+                out.writeInt(mDigestBytes.length);
+                for (int i = 0; i < mDigestBytes.length; i++) {
+                    out.writeByteArray(mDigestBytes[i]);
+                }
+            }
+            out.writeIntArray(mDigestPrefix);
+        }
+
+        @SuppressWarnings("hiding")
+        public static final Parcelable.Creator<EphemeralDigest> CREATOR =
+                new Parcelable.Creator<EphemeralDigest>() {
+            public EphemeralDigest createFromParcel(Parcel in) {
+                return new EphemeralDigest(in);
+            }
+
+            public EphemeralDigest[] newArray(int size) {
+                return new EphemeralDigest[size];
+            }
+        };
+    }
 }
diff --git a/core/java/android/content/pm/IOtaDexopt.aidl b/core/java/android/content/pm/IOtaDexopt.aidl
index 8f38d6f..786a77f 100644
--- a/core/java/android/content/pm/IOtaDexopt.aidl
+++ b/core/java/android/content/pm/IOtaDexopt.aidl
@@ -42,6 +42,12 @@
     boolean isDone();
 
     /**
+     * Return the progress (0..1) made in this session. When {@link #isDone() isDone} returns
+     * true, the progress value will be 1.
+     */
+    float getProgress();
+
+    /**
      * Optimize the next package. Note: this command is synchronous, that is, only returns after
      * the package has been dexopted (or dexopting failed).
      */
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 2ba24f6..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);
 
@@ -39,6 +41,11 @@
 
     boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId);
 
+    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);
@@ -47,6 +54,8 @@
 
     int getIconMaxDimensions(String packageName, int userId);
 
+    void reportShortcutUsed(String packageName, String shortcutId, int userId);
+
     void resetThrottling(); // system only API for developer opsions
 
     void onApplicationActive(String packageName, int userId); // system only API for sysUI
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 8ca27c5..6cb50fc 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -24,7 +24,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -34,8 +40,10 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.DisplayMetrics;
 import android.util.Log;
 
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -166,8 +174,6 @@
          * @param shortcuts all shortcuts from the package (dynamic and/or pinned).  Only "key"
          *    information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
          * @param user The UserHandle of the profile that generated the change.
-         *
-         * @hide
          */
         public void onShortcutsChanged(@NonNull String packageName,
                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
@@ -176,8 +182,6 @@
 
     /**
      * Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
-     *
-     * @hide
      */
     public static class ShortcutQuery {
         /**
@@ -191,6 +195,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.
          */
@@ -201,6 +210,7 @@
                 value = {
                         FLAG_GET_DYNAMIC,
                         FLAG_GET_PINNED,
+                        FLAG_GET_MANIFEST,
                         FLAG_GET_KEY_FIELDS_ONLY,
                 })
         @Retention(RetentionPolicy.SOURCE)
@@ -227,37 +237,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.
+         * 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;
         }
     }
 
@@ -426,8 +443,6 @@
      * the user is trying a new launcher application.  The user may decide to change the default
      * launcher to the calling application again, so even if a launcher application loses
      * this permission, it does <b>not</b> have to purge pinned shortcut information.
-     *
-     * @hide
      */
     public boolean hasShortcutHostPermission() {
         try {
@@ -447,8 +462,6 @@
      * @param user The UserHandle of the profile.
      *
      * @return the IDs of {@link ShortcutInfo}s that match the query.
-     *
-     * @hide
      */
     @Nullable
     public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
@@ -488,8 +501,6 @@
      * @param packageName The target package name.
      * @param shortcutIds The IDs of the shortcut to be pinned.
      * @param user The UserHandle of the profile.
-     *
-     * @hide
      */
     public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
             @NonNull UserHandle user) {
@@ -529,12 +540,10 @@
      * #hasShortcutHostPermission()}.
      *
      * @param shortcut The target shortcut.
-     *
-     * @hide
      */
     public ParcelFileDescriptor getShortcutIconFd(
             @NonNull ShortcutInfo shortcut) {
-        return getShortcutIconFd(shortcut.getPackageName(), shortcut.getId(),
+        return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
                 shortcut.getUserId());
     }
 
@@ -548,8 +557,6 @@
      * @param packageName The target package name.
      * @param shortcutId The ID of the shortcut to lad rom.
      * @param user The UserHandle of the profile.
-     *
-     * @hide
      */
     public ParcelFileDescriptor getShortcutIconFd(
             @NonNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user) {
@@ -567,6 +574,63 @@
     }
 
     /**
+     * Returns the icon for this shortcut, without any badging for the profile.
+     *
+     * @param density The preferred density of the icon, zero for default density. Use
+     * density DPI values from {@link DisplayMetrics}.
+     * @see #getShortcutBadgedIconDrawable(ShortcutInfo, int)
+     * @see DisplayMetrics
+     * @return The drawable associated with the shortcut.
+     */
+    public Drawable getShortcutIconDrawable(@NonNull ShortcutInfo shortcut, int density) {
+        if (shortcut.hasIconFile()) {
+            final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut);
+            if (pfd == null) {
+                return null;
+            }
+            try {
+                final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
+                return (bmp == null) ? null : new BitmapDrawable(mContext.getResources(), bmp);
+            } finally {
+                try {
+                    pfd.close();
+                } catch (IOException ignore) {
+                }
+            }
+        } else if (shortcut.hasIconResource()) {
+            try {
+                final int resId = shortcut.getIconResourceId();
+                if (resId == 0) {
+                    return null; // Shouldn't happen but just in case.
+                }
+                final ApplicationInfo ai = getApplicationInfo(shortcut.getPackage(),
+                        /* flags =*/ 0, shortcut.getUserHandle());
+                final Resources res = mContext.getPackageManager().getResourcesForApplication(ai);
+                return res.getDrawableForDensity(resId, density);
+            } catch (NameNotFoundException | Resources.NotFoundException e) {
+                return null;
+            }
+        } else {
+            return null; // Has no icon.
+        }
+    }
+
+    /**
+     * Returns the shortcut icon with badging appropriate for the profile.
+     *
+     * @param density Optional density for the icon, or 0 to use the default density. Use
+     * {@link DisplayMetrics} for DPI values.
+     * @see DisplayMetrics
+     * @return A badged icon for the shortcut.
+     */
+    public Drawable getShortcutBadgedIconDrawable(ShortcutInfo shortcut, int density) {
+        final Drawable originalIcon = getShortcutIconDrawable(shortcut, density);
+
+        return (originalIcon == null) ? null : mContext.getPackageManager().getUserBadgedIcon(
+                originalIcon, shortcut.getUserHandle());
+    }
+
+    /**
      * Launches a shortcut.
      *
      * <p>Callers must be allowed to access the shortcut information, as defined in {@link
@@ -579,8 +643,6 @@
      * @param user The UserHandle of the profile.
      * @return {@code false} when the shortcut is no longer valid (e.g. the creator application
      *   has been uninstalled). {@code true} when the shortcut is still valid.
-     *
-     * @hide
      */
     public boolean startShortcut(@NonNull String packageName, @NonNull String shortcutId,
             @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
@@ -600,12 +662,10 @@
      * @param startActivityOptions Options to pass to startActivity.
      * @return {@code false} when the shortcut is no longer valid (e.g. the creator application
      *   has been uninstalled). {@code true} when the shortcut is still valid.
-     *
-     * @hide
      */
     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/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index adf9fe6..f59a7dd 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -270,6 +270,7 @@
         final int nameRes;
         final int labelRes;
         final int iconRes;
+        final int roundIconRes;
         final int logoRes;
         final int bannerRes;
 
@@ -277,7 +278,8 @@
         TypedArray sa;
 
         ParsePackageItemArgs(Package _owner, String[] _outError,
-                int _nameRes, int _labelRes, int _iconRes, int _logoRes, int _bannerRes) {
+                int _nameRes, int _labelRes, int _iconRes, int _roundIconRes, int _logoRes,
+                int _bannerRes) {
             owner = _owner;
             outError = _outError;
             nameRes = _nameRes;
@@ -285,6 +287,7 @@
             iconRes = _iconRes;
             logoRes = _logoRes;
             bannerRes = _bannerRes;
+            roundIconRes = _roundIconRes;
         }
     }
 
@@ -296,10 +299,12 @@
         int flags;
 
         ParseComponentArgs(Package _owner, String[] _outError,
-                int _nameRes, int _labelRes, int _iconRes, int _logoRes, int _bannerRes,
+                int _nameRes, int _labelRes, int _iconRes, int _roundIconRes, int _logoRes,
+                int _bannerRes,
                 String[] _sepProcesses, int _processRes,
                 int _descriptionRes, int _enabledRes) {
-            super(_owner, _outError, _nameRes, _labelRes, _iconRes, _logoRes, _bannerRes);
+            super(_owner, _outError, _nameRes, _labelRes, _iconRes, _roundIconRes, _logoRes,
+                    _bannerRes);
             sepProcesses = _sepProcesses;
             processRes = _processRes;
             descriptionRes = _descriptionRes;
@@ -2506,12 +2511,12 @@
 
         TypedArray sa = res.obtainAttributes(parser,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup);
-
         if (!parsePackageItemInfo(owner, perm.info, outError,
-                "<permission-group>", sa,
+                "<permission-group>", sa, true /*nameRequired*/,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_name,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_label,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon,
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_roundIcon,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_logo,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_banner)) {
             sa.recycle();
@@ -2552,10 +2557,11 @@
                 com.android.internal.R.styleable.AndroidManifestPermission);
 
         if (!parsePackageItemInfo(owner, perm.info, outError,
-                "<permission>", sa,
+                "<permission>", sa, true /*nameRequired*/,
                 com.android.internal.R.styleable.AndroidManifestPermission_name,
                 com.android.internal.R.styleable.AndroidManifestPermission_label,
                 com.android.internal.R.styleable.AndroidManifestPermission_icon,
+                com.android.internal.R.styleable.AndroidManifestPermission_roundIcon,
                 com.android.internal.R.styleable.AndroidManifestPermission_logo,
                 com.android.internal.R.styleable.AndroidManifestPermission_banner)) {
             sa.recycle();
@@ -2621,10 +2627,11 @@
                 com.android.internal.R.styleable.AndroidManifestPermissionTree);
 
         if (!parsePackageItemInfo(owner, perm.info, outError,
-                "<permission-tree>", sa,
+                "<permission-tree>", sa, true /*nameRequired*/,
                 com.android.internal.R.styleable.AndroidManifestPermissionTree_name,
                 com.android.internal.R.styleable.AndroidManifestPermissionTree_label,
                 com.android.internal.R.styleable.AndroidManifestPermissionTree_icon,
+                com.android.internal.R.styleable.AndroidManifestPermissionTree_roundIcon,
                 com.android.internal.R.styleable.AndroidManifestPermissionTree_logo,
                 com.android.internal.R.styleable.AndroidManifestPermissionTree_banner)) {
             sa.recycle();
@@ -2671,6 +2678,7 @@
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_name,
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_label,
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_icon,
+                    com.android.internal.R.styleable.AndroidManifestInstrumentation_roundIcon,
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_logo,
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_banner);
             mParseInstrumentationArgs.tag = "<instrumentation>";
@@ -2736,10 +2744,21 @@
         TypedArray sa = res.obtainAttributes(parser,
                 com.android.internal.R.styleable.AndroidManifestApplication);
 
-        String name = sa.getNonConfigurationString(
-                com.android.internal.R.styleable.AndroidManifestApplication_name, 0);
-        if (name != null) {
-            ai.className = buildClassName(pkgName, name, outError);
+        if (!parsePackageItemInfo(owner, ai, outError,
+                "<application>", sa, false /*nameRequired*/,
+                com.android.internal.R.styleable.AndroidManifestApplication_name,
+                com.android.internal.R.styleable.AndroidManifestApplication_label,
+                com.android.internal.R.styleable.AndroidManifestApplication_icon,
+                com.android.internal.R.styleable.AndroidManifestApplication_roundIcon,
+                com.android.internal.R.styleable.AndroidManifestApplication_logo,
+                com.android.internal.R.styleable.AndroidManifestApplication_banner)) {
+            sa.recycle();
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+            return false;
+        }
+
+        if (ai.name != null) {
+            ai.className = buildClassName(pkgName, ai.name, outError);
             if (ai.className == null) {
                 sa.recycle();
                 mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
@@ -2810,18 +2829,6 @@
             }
         }
 
-        TypedValue v = sa.peekValue(
-                com.android.internal.R.styleable.AndroidManifestApplication_label);
-        if (v != null && (ai.labelRes=v.resourceId) == 0) {
-            ai.nonLocalizedLabel = v.coerceToString();
-        }
-
-        ai.icon = sa.getResourceId(
-                com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
-        ai.logo = sa.getResourceId(
-                com.android.internal.R.styleable.AndroidManifestApplication_logo, 0);
-        ai.banner = sa.getResourceId(
-                com.android.internal.R.styleable.AndroidManifestApplication_banner, 0);
         ai.theme = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestApplication_theme, 0);
         ai.descriptionRes = sa.getResourceId(
@@ -3335,25 +3342,35 @@
         return true;
     }
 
-    private boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo,
-            String[] outError, String tag, TypedArray sa,
-            int nameRes, int labelRes, int iconRes, int logoRes, int bannerRes) {
+    private static boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo,
+            String[] outError, String tag, TypedArray sa, boolean nameRequired,
+            int nameRes, int labelRes, int iconRes, int roundIconRes, int logoRes, int bannerRes) {
         String name = sa.getNonConfigurationString(nameRes, 0);
         if (name == null) {
-            outError[0] = tag + " does not specify android:name";
-            return false;
+            if (nameRequired) {
+                outError[0] = tag + " does not specify android:name";
+                return false;
+            }
+        } else {
+            outInfo.name
+                = buildClassName(owner.applicationInfo.packageName, name, outError);
+            if (outInfo.name == null) {
+                return false;
+            }
         }
 
-        outInfo.name
-            = buildClassName(owner.applicationInfo.packageName, name, outError);
-        if (outInfo.name == null) {
-            return false;
-        }
-
-        int iconVal = sa.getResourceId(iconRes, 0);
-        if (iconVal != 0) {
-            outInfo.icon = iconVal;
+        final boolean useRoundIcon =
+                Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+        int roundIconVal = useRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0;
+        if (roundIconVal != 0) {
+            outInfo.icon = roundIconVal;
             outInfo.nonLocalizedLabel = null;
+        } else {
+            int iconVal = sa.getResourceId(iconRes, 0);
+            if (iconVal != 0) {
+                outInfo.icon = iconVal;
+                outInfo.nonLocalizedLabel = null;
+            }
         }
 
         int logoVal = sa.getResourceId(logoRes, 0);
@@ -3387,6 +3404,7 @@
                     R.styleable.AndroidManifestActivity_name,
                     R.styleable.AndroidManifestActivity_label,
                     R.styleable.AndroidManifestActivity_icon,
+                    R.styleable.AndroidManifestActivity_roundIcon,
                     R.styleable.AndroidManifestActivity_logo,
                     R.styleable.AndroidManifestActivity_banner,
                     mSeparateProcesses,
@@ -3761,6 +3779,7 @@
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_name,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_label,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
+                    com.android.internal.R.styleable.AndroidManifestActivityAlias_roundIcon,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_logo,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_banner,
                     mSeparateProcesses,
@@ -3915,6 +3934,7 @@
                     com.android.internal.R.styleable.AndroidManifestProvider_name,
                     com.android.internal.R.styleable.AndroidManifestProvider_label,
                     com.android.internal.R.styleable.AndroidManifestProvider_icon,
+                    com.android.internal.R.styleable.AndroidManifestProvider_roundIcon,
                     com.android.internal.R.styleable.AndroidManifestProvider_logo,
                     com.android.internal.R.styleable.AndroidManifestProvider_banner,
                     mSeparateProcesses,
@@ -4234,6 +4254,7 @@
                     com.android.internal.R.styleable.AndroidManifestService_name,
                     com.android.internal.R.styleable.AndroidManifestService_label,
                     com.android.internal.R.styleable.AndroidManifestService_icon,
+                    com.android.internal.R.styleable.AndroidManifestService_roundIcon,
                     com.android.internal.R.styleable.AndroidManifestService_logo,
                     com.android.internal.R.styleable.AndroidManifestService_banner,
                     mSeparateProcesses,
@@ -4550,8 +4571,16 @@
             outInfo.nonLocalizedLabel = v.coerceToString();
         }
 
-        outInfo.icon = sa.getResourceId(
-                com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
+        final boolean useRoundIcon =
+                Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+        int roundIconVal = useRoundIcon ? sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestIntentFilter_roundIcon, 0) : 0;
+        if (roundIconVal != 0) {
+            outInfo.icon = roundIconVal;
+        } else {
+            outInfo.icon = sa.getResourceId(
+                    com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
+        }
 
         outInfo.logo = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0);
@@ -5180,45 +5209,13 @@
         public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) {
             owner = args.owner;
             intents = new ArrayList<II>(0);
-            String name = args.sa.getNonConfigurationString(args.nameRes, 0);
-            if (name == null) {
+            if (parsePackageItemInfo(args.owner, outInfo, args.outError, args.tag, args.sa,
+                    true /*nameRequired*/, args.nameRes, args.labelRes, args.iconRes,
+                    args.roundIconRes, args.logoRes, args.bannerRes)) {
+                className = outInfo.name;
+            } else {
                 className = null;
-                args.outError[0] = args.tag + " does not specify android:name";
-                return;
             }
-
-            outInfo.name
-                = buildClassName(owner.applicationInfo.packageName, name, args.outError);
-            if (outInfo.name == null) {
-                className = null;
-                args.outError[0] = args.tag + " does not have valid android:name";
-                return;
-            }
-
-            className = outInfo.name;
-
-            int iconVal = args.sa.getResourceId(args.iconRes, 0);
-            if (iconVal != 0) {
-                outInfo.icon = iconVal;
-                outInfo.nonLocalizedLabel = null;
-            }
-
-            int logoVal = args.sa.getResourceId(args.logoRes, 0);
-            if (logoVal != 0) {
-                outInfo.logo = logoVal;
-            }
-
-            int bannerVal = args.sa.getResourceId(args.bannerRes, 0);
-            if (bannerVal != 0) {
-                outInfo.banner = bannerVal;
-            }
-
-            TypedValue v = args.sa.peekValue(args.labelRes);
-            if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
-                outInfo.nonLocalizedLabel = v.coerceToString();
-            }
-
-            outInfo.packageName = owner.packageName;
         }
 
         public Component(final ParseComponentArgs args, final ComponentInfo outInfo) {
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 4340d04..896fa43 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -22,6 +22,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -29,7 +31,9 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -50,26 +54,49 @@
  * </ul>
  *
  * @see {@link ShortcutManager}.
- *
- * @hide
  */
 public final class ShortcutInfo implements Parcelable {
-    /* @hide */
+    static final String TAG = "Shortcut";
+
+    private static final String RES_TYPE_STRING = "string";
+
+    private static final String ANDROID_PACKAGE_NAME = "android";
+
+    private static final int IMPLICIT_RANK_MASK = 0x7fffffff;
+
+    private static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK;
+
+    /** @hide */
+    public static final int RANK_NOT_SET = Integer.MAX_VALUE;
+
+    /** @hide */
     public static final int FLAG_DYNAMIC = 1 << 0;
 
-    /* @hide */
+    /** @hide */
     public static final int FLAG_PINNED = 1 << 1;
 
-    /* @hide */
+    /** @hide */
     public static final int FLAG_HAS_ICON_RES = 1 << 2;
 
-    /* @hide */
+    /** @hide */
     public static final int FLAG_HAS_ICON_FILE = 1 << 3;
 
-    /* @hide */
+    /** @hide */
     public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
 
     /** @hide */
+    public static final int FLAG_MANIFEST = 1 << 5;
+
+    /** @hide */
+    public static final int FLAG_DISABLED = 1 << 6;
+
+    /** @hide */
+    public static final int FLAG_STRINGS_RESOLVED = 1 << 7;
+
+    /** @hide */
+    public static final int FLAG_IMMUTABLE = 1 << 8;
+
+    /** @hide */
     @IntDef(flag = true,
             value = {
             FLAG_DYNAMIC,
@@ -77,26 +104,34 @@
             FLAG_HAS_ICON_RES,
             FLAG_HAS_ICON_FILE,
             FLAG_KEY_FIELDS_ONLY,
+            FLAG_MANIFEST,
+            FLAG_DISABLED,
+            FLAG_STRINGS_RESOLVED,
+            FLAG_IMMUTABLE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ShortcutFlags {}
 
     // Cloning options.
 
-    /* @hide */
+    /** @hide */
     private static final int CLONE_REMOVE_ICON = 1 << 0;
 
-    /* @hide */
+    /** @hide */
     private static final int CLONE_REMOVE_INTENT = 1 << 1;
 
-    /* @hide */
+    /** @hide */
     public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2;
 
-    /* @hide */
-    public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON;
+    /** @hide */
+    public static final int CLONE_REMOVE_RES_NAMES = 1 << 3;
 
-    /* @hide */
-    public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT;
+    /** @hide */
+    public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES;
+
+    /** @hide */
+    public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT
+            | CLONE_REMOVE_RES_NAMES;
 
     /** @hide */
     @IntDef(flag = true,
@@ -104,6 +139,7 @@
                     CLONE_REMOVE_ICON,
                     CLONE_REMOVE_INTENT,
                     CLONE_REMOVE_NON_KEY_INFO,
+                    CLONE_REMOVE_RES_NAMES,
                     CLONE_REMOVE_FOR_CREATOR,
                     CLONE_REMOVE_FOR_LAUNCHER
             })
@@ -121,33 +157,57 @@
     private final String mPackageName;
 
     @Nullable
-    private ComponentName mActivityComponent;
+    private ComponentName mActivity;
 
     @Nullable
     private Icon mIcon;
 
-    @NonNull
-    private String mTitle;
+    private int mTitleResId;
+
+    private String mTitleResName;
 
     @Nullable
-    private String mText;
+    private CharSequence mTitle;
 
-    @NonNull
+    private int mTextResId;
+
+    private String mTextResName;
+
+    @Nullable
+    private CharSequence mText;
+
+    private int mDisabledMessageResId;
+
+    private String mDisabledMessageResName;
+
+    @Nullable
+    private CharSequence mDisabledMessage;
+
+    @Nullable
     private ArraySet<String> mCategories;
 
     /**
      * Intent *with extras removed*.
      */
-    @NonNull
+    @Nullable
     private Intent mIntent;
 
     /**
      * Extras for the intent.
      */
-    @NonNull
+    @Nullable
     private PersistableBundle mIntentPersistableExtras;
 
-    private int mWeight;
+    private int mRank;
+
+    /**
+     * Internally used for auto-rank-adjustment.
+     *
+     * RANK_CHANGED_BIT is used to denote that the rank of a shortcut is changing.
+     * The rest of the bits are used to denote the order in which shortcuts are passed to
+     * APIs, which is used to preserve the argument order when ranks are tie.
+     */
+    private int mImplicitRank;
 
     @Nullable
     private PersistableBundle mExtras;
@@ -159,7 +219,9 @@
     private int mFlags;
 
     // Internal use only.
-    private int mIconResourceId;
+    private int mIconResId;
+
+    private String mIconResName;
 
     // Internal use only.
     @Nullable
@@ -175,10 +237,14 @@
         // 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;
         mText = b.mText;
+        mTextResId = b.mTextResId;
+        mDisabledMessage = b.mDisabledMessage;
+        mDisabledMessageResId = b.mDisabledMessageResId;
         mCategories = clone(b.mCategories);
         mIntent = b.mIntent;
         if (mIntent != null) {
@@ -188,7 +254,7 @@
                 mIntentPersistableExtras = new PersistableBundle(intentExtras);
             }
         }
-        mWeight = b.mWeight;
+        mRank = b.mRank;
         mExtras = b.mExtras;
         updateTimestamp();
     }
@@ -204,7 +270,10 @@
      */
     public void enforceMandatoryFields() {
         Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
-        Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided");
+        Preconditions.checkNotNull(mActivity, "Activity must be provided");
+        if (mTitle == null && mTitleResId == 0) {
+            throw new IllegalArgumentException("Short label must be provided");
+        }
         Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
     }
 
@@ -219,10 +288,10 @@
         mLastChangedTimestamp = source.mLastChangedTimestamp;
 
         // Just always keep it since it's cheep.
-        mIconResourceId = source.mIconResourceId;
+        mIconResId = source.mIconResId;
 
         if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
-            mActivityComponent = source.mActivityComponent;
+            mActivity = source.mActivity;
 
             if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
                 mIcon = source.mIcon;
@@ -230,14 +299,25 @@
             }
 
             mTitle = source.mTitle;
+            mTitleResId = source.mTitleResId;
             mText = source.mText;
+            mTextResId = source.mTextResId;
+            mDisabledMessage = source.mDisabledMessage;
+            mDisabledMessageResId = source.mDisabledMessageResId;
             mCategories = clone(source.mCategories);
             if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
                 mIntent = source.mIntent;
                 mIntentPersistableExtras = source.mIntentPersistableExtras;
             }
-            mWeight = source.mWeight;
+            mRank = source.mRank;
             mExtras = source.mExtras;
+
+            if ((cloneFlags & CLONE_REMOVE_RES_NAMES) == 0) {
+                mTitleResName = source.mTitleResName;
+                mTextResName = source.mTextResName;
+                mDisabledMessageResName = source.mDisabledMessageResName;
+                mIconResName = source.mIconResName;
+            }
         } else {
             // Set this bit.
             mFlags |= FLAG_KEY_FIELDS_ONLY;
@@ -245,6 +325,221 @@
     }
 
     /**
+     * Load a string resource from the publisher app.
+     *
+     * @param resId resource ID
+     * @param defValue default value to be returned when the specified resource isn't found.
+     */
+    private CharSequence getResourceString(Resources res, int resId, CharSequence defValue) {
+        try {
+            return res.getString(resId);
+        } catch (NotFoundException e) {
+            Log.e(TAG, "Resource for ID=" + resId + " not found in package " + mPackageName);
+            return defValue;
+        }
+    }
+
+    /**
+     * Load the string resources for the text fields and set them to the actual value fields.
+     * This will set {@link #FLAG_STRINGS_RESOLVED}.
+     *
+     * @param res {@link Resources} for the publisher.  Must have been loaded with
+     * {@link PackageManager#getResourcesForApplicationAsUser}.
+     *
+     * @hide
+     */
+    public void resolveResourceStrings(@NonNull Resources res) {
+        mFlags |= FLAG_STRINGS_RESOLVED;
+
+        if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)) {
+            return; // Bail early.
+        }
+
+        if (mTitleResId != 0) {
+            mTitle = getResourceString(res, mTitleResId, mTitle);
+        }
+        if (mTextResId != 0) {
+            mText = getResourceString(res, mTextResId, mText);
+        }
+        if (mDisabledMessageResId != 0) {
+            mDisabledMessage = getResourceString(res, mDisabledMessageResId, mDisabledMessage);
+        }
+    }
+
+    /**
+     * Look up resource name for a given resource ID.
+     *
+     * @return a simple resource name (e.g. "text_1") when {@code withType} is false, or with the
+     * type (e.g. "string/text_1").
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static String lookUpResourceName(@NonNull Resources res, int resId, boolean withType,
+            @NonNull String packageName) {
+        if (resId == 0) {
+            return null;
+        }
+        try {
+            final String fullName = res.getResourceName(resId);
+
+            if (ANDROID_PACKAGE_NAME.equals(getResourcePackageName(fullName))) {
+                // If it's a framework resource, the value won't change, so just return the ID
+                // value as a string.
+                return String.valueOf(resId);
+            }
+            return withType ? getResourceTypeAndEntryName(fullName)
+                    : getResourceEntryName(fullName);
+        } catch (NotFoundException e) {
+            Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
+                    + ". Resource IDs may change when the application is upgraded, and the system"
+                    + " may not be able to find the correct resource.");
+            return null;
+        }
+    }
+
+    /**
+     * Extract the package name from a fully-donated resource name.
+     * e.g. "com.android.app1:drawable/icon1" -> "com.android.app1"
+     * @hide
+     */
+    @VisibleForTesting
+    public static String getResourcePackageName(@NonNull String fullResourceName) {
+        final int p1 = fullResourceName.indexOf(':');
+        if (p1 < 0) {
+            return null;
+        }
+        return fullResourceName.substring(0, p1);
+    }
+
+    /**
+     * Extract the type name from a fully-donated resource name.
+     * e.g. "com.android.app1:drawable/icon1" -> "drawable"
+     * @hide
+     */
+    @VisibleForTesting
+    public static String getResourceTypeName(@NonNull String fullResourceName) {
+        final int p1 = fullResourceName.indexOf(':');
+        if (p1 < 0) {
+            return null;
+        }
+        final int p2 = fullResourceName.indexOf('/', p1 + 1);
+        if (p2 < 0) {
+            return null;
+        }
+        return fullResourceName.substring(p1 + 1, p2);
+    }
+
+    /**
+     * Extract the type name + the entry name from a fully-donated resource name.
+     * e.g. "com.android.app1:drawable/icon1" -> "drawable/icon1"
+     * @hide
+     */
+    @VisibleForTesting
+    public static String getResourceTypeAndEntryName(@NonNull String fullResourceName) {
+        final int p1 = fullResourceName.indexOf(':');
+        if (p1 < 0) {
+            return null;
+        }
+        return fullResourceName.substring(p1 + 1);
+    }
+
+    /**
+     * Extract the entry name from a fully-donated resource name.
+     * e.g. "com.android.app1:drawable/icon1" -> "icon1"
+     * @hide
+     */
+    @VisibleForTesting
+    public static String getResourceEntryName(@NonNull String fullResourceName) {
+        final int p1 = fullResourceName.indexOf('/');
+        if (p1 < 0) {
+            return null;
+        }
+        return fullResourceName.substring(p1 + 1);
+    }
+
+    /**
+     * Return the resource ID for a given resource ID.
+     *
+     * Basically its' a wrapper over {@link Resources#getIdentifier(String, String, String)}, except
+     * if {@code resourceName} is an integer then it'll just return its value.  (Which also the
+     * aforementioned method would do internally, but not documented, so doing here explicitly.)
+     *
+     * @param res {@link Resources} for the publisher.  Must have been loaded with
+     * {@link PackageManager#getResourcesForApplicationAsUser}.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static int lookUpResourceId(@NonNull Resources res, @Nullable String resourceName,
+            @Nullable String resourceType, String packageName) {
+        if (resourceName == null) {
+            return 0;
+        }
+        try {
+            try {
+                // It the name can be parsed as an integer, just use it.
+                return Integer.parseInt(resourceName);
+            } catch (NumberFormatException ignore) {
+            }
+
+            return res.getIdentifier(resourceName, resourceType, packageName);
+        } catch (NotFoundException e) {
+            Log.e(TAG, "Resource ID for name=" + resourceName + " not found in package "
+                    + packageName);
+            return 0;
+        }
+    }
+
+    /**
+     * Look up resource names from the resource IDs for the icon res and the text fields, and fill
+     * in the resource name fields.
+     *
+     * @param res {@link Resources} for the publisher.  Must have been loaded with
+     * {@link PackageManager#getResourcesForApplicationAsUser}.
+     *
+     * @hide
+     */
+    public void lookupAndFillInResourceNames(@NonNull Resources res) {
+        if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)
+                && (mIconResId == 0)) {
+            return; // Bail early.
+        }
+
+        // We don't need types for strings because their types are always "string".
+        mTitleResName = lookUpResourceName(res, mTitleResId, /*withType=*/ false, mPackageName);
+        mTextResName = lookUpResourceName(res, mTextResId, /*withType=*/ false, mPackageName);
+        mDisabledMessageResName = lookUpResourceName(res, mDisabledMessageResId,
+                /*withType=*/ false, mPackageName);
+
+        // But icons have multiple possible types, so include the type.
+        mIconResName = lookUpResourceName(res, mIconResId, /*withType=*/ true, mPackageName);
+    }
+
+    /**
+     * Look up resource IDs from the resource names for the icon res and the text fields, and fill
+     * in the resource ID fields.
+     *
+     * This is called when an app is updated.
+     *
+     * @hide
+     */
+    public void lookupAndFillInResourceIds(@NonNull Resources res) {
+        if ((mTitleResName == null) && (mTextResName == null) && (mDisabledMessageResName == null)
+                && (mIconResName == null)) {
+            return; // Bail early.
+        }
+
+        mTitleResId = lookUpResourceId(res, mTitleResName, RES_TYPE_STRING, mPackageName);
+        mTextResId = lookUpResourceId(res, mTextResName, RES_TYPE_STRING, mPackageName);
+        mDisabledMessageResId = lookUpResourceId(res, mDisabledMessageResName, RES_TYPE_STRING,
+                mPackageName);
+
+        // mIconResName already contains the type, so the third argument is not needed.
+        mIconResId = lookUpResourceId(res, mIconResName, null, mPackageName);
+    }
+
+    /**
      * Copy a {@link ShortcutInfo}, optionally removing fields.
      * @hide
      */
@@ -253,33 +548,70 @@
     }
 
     /**
+     * @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.
+     * will be overwritten.  The timestamp will *not* be updated to be consistent with other
+     * setters (and also the clock is not injectable in this file).
      *
      * - Flags will not change
      * - 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) {
             mIcon = source.mIcon;
+
+            mIconResId = 0;
+            mIconResName = null;
+            mBitmapPath = null;
         }
         if (source.mTitle != null) {
             mTitle = source.mTitle;
+            mTitleResId = 0;
+            mTitleResName = null;
+        } else if (source.mTitleResId != 0) {
+            mTitle = null;
+            mTitleResId = source.mTitleResId;
+            mTitleResName = null;
         }
+
         if (source.mText != null) {
             mText = source.mText;
+            mTextResId = 0;
+            mTextResName = null;
+        } else if (source.mTextResId != 0) {
+            mText = null;
+            mTextResId = source.mTextResId;
+            mTextResName = null;
+        }
+        if (source.mDisabledMessage != null) {
+            mDisabledMessage = source.mDisabledMessage;
+            mDisabledMessageResId = 0;
+            mDisabledMessageResName = null;
+        } else if (source.mDisabledMessageResId != 0) {
+            mDisabledMessage = null;
+            mDisabledMessageResId = source.mDisabledMessageResId;
+            mDisabledMessageResName = null;
         }
         if (source.mCategories != null) {
             mCategories = clone(source.mCategories);
@@ -288,14 +620,12 @@
             mIntent = source.mIntent;
             mIntentPersistableExtras = source.mIntentPersistableExtras;
         }
-        if (source.mWeight != 0) {
-            mWeight = source.mWeight;
+        if (source.mRank != RANK_NOT_SET) {
+            mRank = source.mRank;
         }
         if (source.mExtras != null) {
             mExtras = source.mExtras;
         }
-
-        updateTimestamp();
     }
 
     /**
@@ -310,7 +640,6 @@
                 throw getInvalidIconException();
         }
         if (icon.hasTint()) {
-            // TODO support it
             throw new IllegalArgumentException("Icons with tints are not supported");
         }
 
@@ -331,19 +660,27 @@
 
         private String mId;
 
-        private ComponentName mActivityComponent;
+        private ComponentName mActivity;
 
         private Icon mIcon;
 
-        private String mTitle;
+        private int mTitleResId;
 
-        private String mText;
+        private CharSequence mTitle;
+
+        private int mTextResId;
+
+        private CharSequence mText;
+
+        private int mDisabledMessageResId;
+
+        private CharSequence mDisabledMessage;
 
         private Set<String> mCategories;
 
         private Intent mIntent;
 
-        private int mWeight;
+        private int mRank = RANK_NOT_SET;
 
         private PersistableBundle mExtras;
 
@@ -373,8 +710,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;
         }
 
@@ -399,26 +736,88 @@
         }
 
         /**
-         * Sets the title of a shortcut.  This is a mandatory field.
+         * @hide We don't support resource strings for dynamic shortcuts for now.  (But unit tests
+         * use it.)
+         */
+        public Builder setShortLabelResId(int shortLabelResId) {
+            Preconditions.checkState(mTitle == null, "shortLabel already set");
+            mTitleResId = shortLabelResId;
+            return this;
+        }
+
+        /**
+         * Sets the short title of a shortcut.  This is a mandatory field.
          *
          * <p>This field is intended for a concise description of a shortcut displayed under
          * an icon.  The recommend max length is 10 characters.
          */
         @NonNull
-        public Builder setTitle(@NonNull String title) {
-            mTitle = Preconditions.checkStringNotEmpty(title, "title");
+        public Builder setShortLabel(@NonNull String shortLabel) {
+            Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
+            mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel");
+            return this;
+        }
+
+        /**
+         * @hide We don't support resource strings for dynamic shortcuts for now.  (But unit tests
+         * use it.)
+         */
+        public Builder setLongLabelResId(int longLabelResId) {
+            Preconditions.checkState(mText == null, "longLabel already set");
+            mTextResId = longLabelResId;
             return this;
         }
 
         /**
          * Sets the text of a shortcut.  This is an optional field.
          *
-         * <p>This field is intended to be more descriptive than the shortcut title.
+         * <p>This field is intended to be more descriptive than the shortcut title.  The launcher
+         * shows this instead of the short title, when it has enough space.
          * The recommend max length is 25 characters.
          */
         @NonNull
-        public Builder setText(@NonNull String text) {
-            mText = Preconditions.checkStringNotEmpty(text, "text");
+        public Builder setLongLabel(@NonNull String longLabel) {
+            Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
+            mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel");
+            return this;
+        }
+
+        /** @hide -- old signature, the internal code still uses it. */
+        public Builder setTitle(@NonNull String value) {
+            return setShortLabel(value);
+        }
+
+        /** @hide -- old signature, the internal code still uses it. */
+        public Builder setTitleResId(int value) {
+            return setShortLabelResId(value);
+        }
+
+        /** @hide -- old signature, the internal code still uses it. */
+        public Builder setText(@NonNull String value) {
+            return setLongLabel(value);
+        }
+
+        /** @hide -- old signature, the internal code still uses it. */
+        public Builder setTextResId(int value) {
+            return setLongLabelResId(value);
+        }
+
+        /**
+         * @hide We don't support resource strings for dynamic shortcuts for now.  (But unit tests
+         * use it.)
+         */
+        public Builder setDisabledMessageResId(int disabledMessageResId) {
+            Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set");
+            mDisabledMessageResId = disabledMessageResId;
+            return this;
+        }
+
+        @NonNull
+        public Builder setDisabledMessage(@NonNull String disabledMessage) {
+            Preconditions.checkState(
+                    mDisabledMessageResId == 0, "disabledMessageResId already set");
+            mDisabledMessage =
+                    Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage");
             return this;
         }
 
@@ -441,16 +840,19 @@
         @NonNull
         public Builder setIntent(@NonNull Intent intent) {
             mIntent = Preconditions.checkNotNull(intent, "intent");
+            Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set");
             return this;
         }
 
         /**
-         * Optionally sets the weight of a shortcut, which will be used by the launcher for sorting.
-         * The larger the weight, the more "important" a shortcut is.
+         * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app
+         * to sort shortcuts.
          */
         @NonNull
-        public Builder setWeight(int weight) {
-            mWeight = weight;
+        public Builder setRank(int rank) {
+            Preconditions.checkArgument((0 <= rank),
+                    "Rank cannot be negative or bigger than MAX_RANK");
+            mRank = rank;
             return this;
         }
 
@@ -485,7 +887,7 @@
      * Return the package name of the creator application.
      */
     @NonNull
-    public String getPackageName() {
+    public String getPackage() {
         return mPackageName;
     }
 
@@ -496,11 +898,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;
     }
 
     /**
@@ -517,25 +919,70 @@
         return mIcon;
     }
 
+    /** @hide -- old signature, the internal code still uses it. */
+    @Nullable
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /** @hide -- old signature, the internal code still uses it. */
+    public int getTitleResId() {
+        return mTitleResId;
+    }
+
+    /** @hide -- old signature, the internal code still uses it. */
+    @Nullable
+    public CharSequence getText() {
+        return mText;
+    }
+
+    /** @hide -- old signature, the internal code still uses it. */
+    public int getTextResId() {
+        return mTextResId;
+    }
+
     /**
-     * Return the shortcut title.
+     * Return the shorter version of the shortcut title.
      *
      * <p>All shortcuts must have a non-empty title, but this method will return null when
      * {@link #hasKeyFieldsOnly()} is true.
      */
     @Nullable
-    public String getTitle() {
+    public CharSequence getShortLabel() {
         return mTitle;
     }
 
+    /** TODO Javadoc */
+    public int getShortLabelResourceId() {
+        return mTitleResId;
+    }
+
     /**
      * Return the shortcut text.
      */
     @Nullable
-    public String getText() {
+    public CharSequence getLongLabel() {
         return mText;
     }
 
+    /** TODO Javadoc */
+    public int getLongLabelResourceId() {
+        return mTextResId;
+    }
+
+    /**
+     * Return the message that should be shown when a shortcut in disabled state is launched.
+     */
+    @Nullable
+    public CharSequence getDisabledMessage() {
+        return mDisabledMessage;
+    }
+
+    /** TODO Javadoc */
+    public int getDisabledMessageResourceId() {
+        return mDisabledMessageResId;
+    }
+
     /**
      * Return the categories.
      */
@@ -585,11 +1032,55 @@
     }
 
     /**
-     * Return the weight of a shortcut, which will be used by Launcher for sorting.
-     * The larger the weight, the more "important" a shortcut is.
+     * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each
+     * {@link #getActivity} for each of the two kinds, dynamic shortcuts and manifest shortcuts.
+     *
+     * <p>Because manifest shortcuts and dynamic shortcuts have overlapping ranks,
+     * when a launcher application shows shortcuts for an activity, it should first show
+     * the manifest shortcuts followed by the dynamic shortcuts.  Within each of those categories,
+     * shortcuts should be sorted by rank in ascending order.
+     *
+     * <p>"Floating" shortcuts (i.e. shortcuts that are neither dynamic nor manifest) will all
+     * have rank 0, because there's no sorting for them.
      */
-    public int getWeight() {
-        return mWeight;
+    public int getRank() {
+        return mRank;
+    }
+
+    /** @hide */
+    public boolean hasRank() {
+        return mRank != RANK_NOT_SET;
+    }
+
+    /** @hide */
+    public void setRank(int rank) {
+        mRank = rank;
+    }
+
+    /** @hide */
+    public void clearImplicitRankAndRankChangedFlag() {
+        mImplicitRank = 0;
+    }
+
+    /** @hide */
+    public void setImplicitRank(int rank) {
+        // Make sure to keep RANK_CHANGED_BIT.
+        mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK);
+    }
+
+    /** @hide */
+    public int getImplicitRank() {
+        return mImplicitRank & IMPLICIT_RANK_MASK;
+    }
+
+    /** @hide */
+    public void setRankChanged() {
+        mImplicitRank |= RANK_CHANGED_BIT;
+    }
+
+    /** @hide */
+    public boolean isRankChanged() {
+        return (mImplicitRank & RANK_CHANGED_BIT) != 0;
     }
 
     /**
@@ -656,6 +1147,68 @@
     }
 
     /**
+     * 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 isManifestShortcut() {
+        return hasFlags(FLAG_MANIFEST);
+    }
+
+    /**
+     * @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.
      *
      * @see LauncherApps#getShortcutIconResId(ShortcutInfo)
@@ -664,6 +1217,16 @@
         return hasFlags(FLAG_HAS_ICON_RES);
     }
 
+    /** @hide */
+    public boolean hasStringResources() {
+        return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0);
+    }
+
+    /** @hide */
+    public boolean hasAnyResources() {
+        return hasIconResource() || hasStringResources();
+    }
+
     /**
      * Return whether a shortcut's icon is stored as a file.
      *
@@ -678,7 +1241,7 @@
      * following fields are available.
      * <ul>
      *     <li>{@link #getId()}
-     *     <li>{@link #getPackageName()}
+     *     <li>{@link #getPackage()}
      *     <li>{@link #getLastChangedTimestamp()}
      *     <li>{@link #isDynamic()}
      *     <li>{@link #isPinned()}
@@ -690,6 +1253,11 @@
         return hasFlags(FLAG_KEY_FIELDS_ONLY);
     }
 
+    /** TODO Javadoc */
+    public boolean hasStringResourcesResolved() {
+        return hasFlags(FLAG_STRINGS_RESOLVED);
+    }
+
     /** @hide */
     public void updateTimestamp() {
         mLastChangedTimestamp = System.currentTimeMillis();
@@ -708,14 +1276,17 @@
 
     /** @hide */
     public void setIconResourceId(int iconResourceId) {
-        mIconResourceId = iconResourceId;
+        if (mIconResId != iconResourceId) {
+            mIconResName = null;
+        }
+        mIconResId = iconResourceId;
     }
 
     /**
      * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true.
      */
     public int getIconResourceId() {
-        return mIconResourceId;
+        return mIconResId;
     }
 
     /** @hide */
@@ -728,25 +1299,90 @@
         mBitmapPath = bitmapPath;
     }
 
+    /** @hide */
+    public void setDisabledMessageResId(int disabledMessageResId) {
+        if (mDisabledMessageResId != disabledMessageResId) {
+            mDisabledMessageResName = null;
+        }
+        mDisabledMessageResId = disabledMessageResId;
+        mDisabledMessage = null;
+    }
+
+    /** @hide */
+    public void setDisabledMessage(String disabledMessage) {
+        mDisabledMessage = disabledMessage;
+        mDisabledMessageResId = 0;
+        mDisabledMessageResName = null;
+    }
+
+    /** @hide */
+    public String getTitleResName() {
+        return mTitleResName;
+    }
+
+    /** @hide */
+    public void setTitleResName(String titleResName) {
+        mTitleResName = titleResName;
+    }
+
+    /** @hide */
+    public String getTextResName() {
+        return mTextResName;
+    }
+
+    /** @hide */
+    public void setTextResName(String textResName) {
+        mTextResName = textResName;
+    }
+
+    /** @hide */
+    public String getDisabledMessageResName() {
+        return mDisabledMessageResName;
+    }
+
+    /** @hide */
+    public void setDisabledMessageResName(String disabledMessageResName) {
+        mDisabledMessageResName = disabledMessageResName;
+    }
+
+    /** @hide */
+    public String getIconResName() {
+        return mIconResName;
+    }
+
+    /** @hide */
+    public void setIconResName(String iconResName) {
+        mIconResName = iconResName;
+    }
+
     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();
-        mText = source.readString();
+        mTitle = source.readCharSequence();
+        mTitleResId = source.readInt();
+        mText = source.readCharSequence();
+        mTextResId = source.readInt();
+        mDisabledMessage = source.readCharSequence();
+        mDisabledMessageResId = source.readInt();
         mIntent = source.readParcelable(cl);
         mIntentPersistableExtras = source.readParcelable(cl);
-        mWeight = source.readInt();
+        mRank = source.readInt();
         mExtras = source.readParcelable(cl);
         mLastChangedTimestamp = source.readLong();
         mFlags = source.readInt();
-        mIconResourceId = source.readInt();
+        mIconResId = source.readInt();
         mBitmapPath = source.readString();
 
+        mIconResName = source.readString();
+        mTitleResName = source.readString();
+        mTextResName = source.readString();
+        mDisabledMessageResName = source.readString();
+
         int N = source.readInt();
         if (N == 0) {
             mCategories = null;
@@ -763,20 +1399,29 @@
         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.writeString(mText);
+        dest.writeCharSequence(mTitle);
+        dest.writeInt(mTitleResId);
+        dest.writeCharSequence(mText);
+        dest.writeInt(mTextResId);
+        dest.writeCharSequence(mDisabledMessage);
+        dest.writeInt(mDisabledMessageResId);
 
         dest.writeParcelable(mIntent, flags);
         dest.writeParcelable(mIntentPersistableExtras, flags);
-        dest.writeInt(mWeight);
+        dest.writeInt(mRank);
         dest.writeParcelable(mExtras, flags);
         dest.writeLong(mLastChangedTimestamp);
         dest.writeInt(mFlags);
-        dest.writeInt(mIconResourceId);
+        dest.writeInt(mIconResId);
         dest.writeString(mBitmapPath);
 
+        dest.writeString(mIconResName);
+        dest.writeString(mTitleResName);
+        dest.writeString(mTextResName);
+        dest.writeString(mDisabledMessageResName);
+
         if (mCategories != null) {
             final int N = mCategories.size();
             dest.writeInt(N);
@@ -823,24 +1468,67 @@
         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(", shortLabel=");
         sb.append(secure ? "***" : mTitle);
+        sb.append(", resId=");
+        sb.append(mTitleResId);
+        sb.append("[");
+        sb.append(mTitleResName);
+        sb.append("]");
 
-        sb.append(", text=");
+        sb.append(", longLabel=");
         sb.append(secure ? "***" : mText);
+        sb.append(", resId=");
+        sb.append(mTextResId);
+        sb.append("[");
+        sb.append(mTextResName);
+        sb.append("]");
+
+        sb.append(", disabledMessage=");
+        sb.append(secure ? "***" : mDisabledMessage);
+        sb.append(", resId=");
+        sb.append(mDisabledMessageResId);
+        sb.append("[");
+        sb.append(mDisabledMessageResName);
+        sb.append("]");
 
         sb.append(", categories=");
         sb.append(mCategories);
@@ -848,8 +1536,8 @@
         sb.append(", icon=");
         sb.append(mIcon);
 
-        sb.append(", weight=");
-        sb.append(mWeight);
+        sb.append(", rank=");
+        sb.append(mRank);
 
         sb.append(", timestamp=");
         sb.append(mLastChangedTimestamp);
@@ -863,13 +1551,13 @@
         sb.append(", extras=");
         sb.append(mExtras);
 
-        sb.append(", flags=");
-        sb.append(mFlags);
-
         if (includeInternalData) {
 
             sb.append(", iconRes=");
-            sb.append(mIconResourceId);
+            sb.append(mIconResId);
+            sb.append("[");
+            sb.append(mIconResName);
+            sb.append("]");
 
             sb.append(", bitmapPath=");
             sb.append(mBitmapPath);
@@ -881,26 +1569,37 @@
 
     /** @hide */
     public ShortcutInfo(
-            @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
-            Icon icon, String title, String text, Set<String> categories, Intent intent,
-            PersistableBundle intentPersistableExtras,
-            int weight, PersistableBundle extras, long lastChangedTimestamp,
-            int flags, int iconResId, String bitmapPath) {
+            @UserIdInt int userId, String id, String packageName, ComponentName activity,
+            Icon icon, CharSequence title, int titleResId, String titleResName,
+            CharSequence text, int textResId, String textResName,
+            CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
+            Set<String> categories,
+            Intent intent, PersistableBundle intentPersistableExtras,
+            int rank, PersistableBundle extras, long lastChangedTimestamp,
+            int flags, int iconResId, String iconResName, String bitmapPath) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
-        mActivityComponent = activityComponent;
+        mActivity = activity;
         mIcon = icon;
         mTitle = title;
+        mTitleResId = titleResId;
+        mTitleResName = titleResName;
         mText = text;
+        mTextResId = textResId;
+        mTextResName = textResName;
+        mDisabledMessage = disabledMessage;
+        mDisabledMessageResId = disabledMessageResId;
+        mDisabledMessageResName = disabledMessageResName;
         mCategories = clone(categories);
         mIntent = intent;
         mIntentPersistableExtras = intentPersistableExtras;
-        mWeight = weight;
+        mRank = rank;
         mExtras = extras;
         mLastChangedTimestamp = lastChangedTimestamp;
         mFlags = flags;
-        mIconResourceId = iconResId;
+        mIconResId = iconResId;
+        mIconResName = iconResName;
         mBitmapPath = bitmapPath;
     }
 }
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 16486e1..36ab804 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -17,8 +17,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.usage.UsageStatsManager;
 import android.content.Context;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -29,14 +30,16 @@
 
 // TODO Enhance javadoc
 /**
+ * <b>TODO: Update to reflect DR changes, such as manifest shortcuts.</b><br>
+ *
  * {@link ShortcutManager} manages shortcuts created by applications.
  *
  * <h3>Dynamic shortcuts and pinned shortcuts</h3>
  *
  * 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.
  *
@@ -50,7 +53,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>
@@ -80,16 +84,24 @@
  *
  * <h3>Backup and Restore</h3>
  *
- * Shortcuts will be backed up and restored across devices.  This means all information, including
- * IDs, must be meaningful on a different device.
+ * Pinned shortcuts will be backed up and restored across devices.  This means all information
+ * within shortcuts, including IDs, must be meaningful on different devices.
+ *
+ * <p>Note that:
+ * <ul>
+ *     <li>Dynamic shortcuts will not be backed up or restored.
+ *     <li>Icons of pinned shortcuts will <b>not</b> be backed up for performance reasons, unless
+ *     they refer to resources.  Instead, launcher applications are supposed to back up and restore
+ *     icons of pinned shortcuts by themselves, and thus from the user's point of view, pinned
+ *     shortcuts will look to have icons restored.
+ * </ul>
+ *
  *
  * <h3>APIs for launcher</h3>
  *
  * Launcher applications should use {@link LauncherApps} to get shortcuts that are published from
  * applications.  Launcher applications can also pin shortcuts with
  * {@link LauncherApps#pinShortcuts(String, List, UserHandle)}.
- *
- * @hide
  */
 public class ShortcutManager {
     private static final String TAG = "ShortcutManager";
@@ -123,7 +135,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 {
@@ -136,7 +148,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() {
@@ -149,8 +161,21 @@
     }
 
     /**
-     * Publish a single dynamic shortcut.  If there's already dynamic or pinned shortcuts with
-     * the same ID, they will all be updated.
+     * 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.
      *
      * <p>This API will be rate-limited.
      *
@@ -169,7 +194,7 @@
     }
 
     /**
-     * Delete a single dynamic shortcut by ID.
+     * Delete dynamic shortcuts by ID.
      */
     public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
         try {
@@ -221,9 +246,60 @@
     }
 
     /**
-     * Return the max number of dynamic shortcuts that each application can have at a time.
+     * TODO Javadoc
      */
-    public int getMaxDynamicShortcutCount() {
+    public void disableShortcuts(@NonNull List<String> shortcutIds) {
+        try {
+            mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+                    /* disabledMessage =*/ null, /* disabledMessageResId =*/ 0,
+                    injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * TODO Javadoc
+     */
+    public void disableShortcuts(@NonNull List<String> shortcutIds, int disabledMessageResId) {
+        try {
+            mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+                    /* disabledMessage =*/ null, disabledMessageResId,
+                    injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * TODO Javadoc
+     */
+    public void disableShortcuts(@NonNull List<String> shortcutIds, String disabledMessage) {
+        try {
+            mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+                    disabledMessage, /* disabledMessageResId =*/ 0,
+                    injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * TODO Javadoc
+     */
+    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) {
@@ -260,16 +336,66 @@
     }
 
     /**
-     * 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();
+        }
+    }
+
+    /**
+     * Applications that publish shortcuts should call this method whenever an action that's
+     * equivalent to an existing shortcut has been taken by the user.  This includes not only when
+     * the user manually taps a shortcut, but when the user takes an equivalent action within the
+     * application -- for example, when a music player application has a shortcut to playlist X,
+     * then the application should not only report it when the playlist is opened from the
+     * shortcut, but also when the user plays the playlist within the application.
+     *
+     * <p>The information is accessible via {@link UsageStatsManager#queryEvents}
+     * Typically, launcher applications use this information to build a prediction model
+     * so that they can promote the shortcuts that are likely to be used at the moment.
+     */
+    public void reportShortcutUsed(String shortcutId) {
+        try {
+            mService.reportShortcutUsed(mContext.getPackageName(), shortcutId,
+                    injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Called internally when an application is considered to have come to foreground
+     * even when technically it's not.  This method resets the throttling for this package.
+     * For example, when the user sends an "inline reply" on an notification, the system UI will
+     * call it.
+     *
+     * @hide
+     */
+    public void onApplicationActive(@NonNull String packageName, @UserIdInt int userId) {
+        try {
+            mService.onApplicationActive(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide injection point */
     @VisibleForTesting
     protected int injectMyUserId() {
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index dd3a36c..6cd8403 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -28,8 +28,8 @@
  */
 public class UserInfo implements Parcelable {
 
-    /** 8 bits for user type */
-    public static final int FLAG_MASK_USER_TYPE = 0x000000FF;
+    /** 16 bits for user type */
+    public static final int FLAG_MASK_USER_TYPE = 0x0000FFFF;
 
     /**
      * *************************** NOTE ***************************
@@ -87,6 +87,11 @@
      */
     public static final int FLAG_EPHEMERAL = 0x00000100;
 
+    /**
+     * User is for demo purposes only and can be removed at any time.
+     */
+    public static final int FLAG_DEMO = 0x00000200;
+
     public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
 
     public int id;
@@ -153,6 +158,10 @@
         return (flags & FLAG_INITIALIZED) == FLAG_INITIALIZED;
     }
 
+    public boolean isDemo() {
+        return (flags & FLAG_DEMO) == FLAG_DEMO;
+    }
+
     /**
      * Returns true if the user is a split system user.
      * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 38279a4..4b21187 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.os.Handler;
 import android.view.Surface;
 
@@ -220,6 +221,53 @@
     public abstract void tearDown(@NonNull Surface surface) throws CameraAccessException;
 
     /**
+     * <p>
+     * Finish the deferred output configurations where the output Surface was not configured before.
+     * </p>
+     * <p>
+     * For camera use cases where a preview and other output configurations need to be configured,
+     * it can take some time for the preview Surface to be ready (e.g., if the preview Surface is
+     * obtained from {@link android.view.SurfaceView}, the SurfaceView is ready after the UI layout
+     * is done, then it takes some time to get the preview Surface).
+     * </p>
+     * <p>
+     * To speed up camera startup time, the application can configure the
+     * {@link CameraCaptureSession} with the desired preview size, and defer the preview output
+     * configuration until the Surface is ready. After the {@link CameraCaptureSession} is created
+     * successfully with this deferred configuration and other normal configurations, the
+     * application can submit requests that don't include deferred output Surfaces. Once the
+     * deferred Surface is ready, the application can set the Surface to the same deferred output
+     * configuration with the {@link OutputConfiguration#setDeferredSurface} method, and then finish
+     * the deferred output configuration via this method, before it can submit requests with this
+     * output target.
+     * </p>
+     * <p>
+     * The output Surfaces included by this list of deferred {@link OutputConfiguration
+     * OutputConfigurations} can be used as {@link CaptureRequest} targets as soon as this call
+     * returns;
+     * </p>
+     * <p>
+     * This method is not supported by Legacy devices.
+     * </p>
+     *
+     * @param deferredOutputConfigs a list of {@link OutputConfiguration OutputConfigurations} that
+     *            have had {@link OutputConfiguration#setDeferredSurface setDeferredSurface} invoked
+     *            with a valid output Surface.
+     * @throws CameraAccessException if the camera device is no longer connected or has encountered
+     *             a fatal error.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *             was explicitly closed, a new session has been created or the camera device has
+     *             been closed. Or if this output configuration was already finished with the
+     *             included surface before.
+     * @throws IllegalArgumentException for invalid output configurations, including ones where the
+     *             source of the Surface is no longer valid or the Surface is from a unsupported
+     *             source.
+     * @hide
+     */
+    public abstract void finishDeferredConfiguration(
+            List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException;
+
+    /**
      * <p>Submit a request for an image to be captured by the camera device.</p>
      *
      * <p>The request defines all the parameters for capturing the single image,
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 6736d34..c23bd5b 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -24,6 +24,7 @@
 import android.hardware.camera2.dispatch.DuckTypingDispatcher;
 import android.hardware.camera2.dispatch.HandlerDispatcher;
 import android.hardware.camera2.dispatch.InvokeDispatcher;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.utils.TaskDrainer;
 import android.hardware.camera2.utils.TaskSingleDrainer;
 import android.os.Handler;
@@ -156,6 +157,12 @@
     }
 
     @Override
+    public void finishDeferredConfiguration(
+            List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException {
+        mDeviceImpl.finishDeferredConfig(deferredOutputConfigs);
+    }
+
+    @Override
     public synchronized int capture(CaptureRequest request, CaptureCallback callback,
             Handler handler) throws CameraAccessException {
         if (request == null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 8cd1da5..1c8e124 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.os.Handler;
@@ -256,6 +257,12 @@
         return mSessionImpl.isAborting();
     }
 
+    @Override
+    public void finishDeferredConfiguration(List<OutputConfiguration> deferredOutputConfigs)
+            throws CameraAccessException {
+        mSessionImpl.finishDeferredConfiguration(deferredOutputConfigs);
+    }
+
     private class WrapperCallback extends StateCallback {
         private final StateCallback mCallback;
 
@@ -263,26 +270,32 @@
             mCallback = callback;
         }
 
+        @Override
         public void onConfigured(CameraCaptureSession session) {
             mCallback.onConfigured(CameraConstrainedHighSpeedCaptureSessionImpl.this);
         }
 
+        @Override
         public void onConfigureFailed(CameraCaptureSession session) {
             mCallback.onConfigureFailed(CameraConstrainedHighSpeedCaptureSessionImpl.this);
         }
 
+        @Override
         public void onReady(CameraCaptureSession session) {
             mCallback.onReady(CameraConstrainedHighSpeedCaptureSessionImpl.this);
         }
 
+        @Override
         public void onActive(CameraCaptureSession session) {
             mCallback.onActive(CameraConstrainedHighSpeedCaptureSessionImpl.this);
         }
 
+        @Override
         public void onClosed(CameraCaptureSession session) {
             mCallback.onClosed(CameraConstrainedHighSpeedCaptureSessionImpl.this);
         }
 
+        @Override
         public void onSurfacePrepared(CameraCaptureSession session, Surface surface) {
             mCallback.onSurfacePrepared(CameraConstrainedHighSpeedCaptureSessionImpl.this,
                     surface);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 0cee114..ee8a6d7 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -407,7 +407,10 @@
                 int streamId = mConfiguredOutputs.keyAt(i);
                 OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i);
 
-                if (!outputs.contains(outConfig)) {
+                if (!outputs.contains(outConfig) || outConfig.isDeferredConfiguration()) {
+                    // Always delete the deferred output configuration when the session
+                    // is created, as the deferred output configuration doesn't have unique surface
+                    // related identifies.
                     deleteList.add(streamId);
                 } else {
                     addSet.remove(outConfig);  // Don't create a stream previously created
@@ -744,6 +747,37 @@
         }
     }
 
+    public void finishDeferredConfig(List<OutputConfiguration> deferredConfigs)
+            throws CameraAccessException {
+        if (deferredConfigs == null || deferredConfigs.size() == 0) {
+            throw new IllegalArgumentException("deferred config is null or empty");
+        }
+
+        synchronized(mInterfaceLock) {
+            for (OutputConfiguration config : deferredConfigs) {
+                int streamId = -1;
+                for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+                    // Have to use equal here, as createCaptureSessionByOutputConfigurations() and
+                    // createReprocessableCaptureSessionByConfigurations() do a copy of the configs.
+                    if (config.equals(mConfiguredOutputs.valueAt(i))) {
+                        streamId = mConfiguredOutputs.keyAt(i);
+                        break;
+                    }
+                }
+                if (streamId == -1) {
+                    throw new IllegalArgumentException("Deferred config is not part of this "
+                            + "session");
+                }
+
+                if (config.getSurface() == null) {
+                    throw new IllegalArgumentException("The deferred config for stream " + streamId
+                            + " must have a non-null surface");
+                }
+                mRemoteDevice.setDeferredConfiguration(streamId, config);
+            }
+        }
+    }
+
     public int capture(CaptureRequest request, CaptureCallback callback, Handler handler)
             throws CameraAccessException {
         if (DEBUG) {
@@ -2005,6 +2039,7 @@
      *
      * <p> Handle binder death for ICameraDeviceUser. Trigger onError.</p>
      */
+    @Override
     public void binderDied() {
         Log.w(TAG, "CameraDevice " + mCameraId + " died unexpectedly");
 
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index ef5f6d7..d77f60b 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -215,5 +215,14 @@
         }
     }
 
+    public void setDeferredConfiguration(int streamId, OutputConfiguration deferredConfig)
+            throws CameraAccessException {
+        try {
+            mRemoteDevice.setDeferredConfiguration(streamId, deferredConfig);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
 
 }
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index acbf214..b9e75ee 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -567,6 +567,13 @@
     }
 
     @Override
+    public void setDeferredConfiguration(int steamId, OutputConfiguration config) {
+        String err = "Set deferred configuration is not supported on legacy devices";
+        Log.e(TAG, err);
+        throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
+    }
+
+    @Override
     public int createInputStream(int width, int height, int format) {
         String err = "Creating input stream is not supported on legacy devices";
         Log.e(TAG, err);
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 61b534b..69c00e9 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -20,6 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.utils.HashCodeHelpers;
 import android.hardware.camera2.utils.SurfaceUtils;
@@ -95,6 +97,21 @@
     }
 
     /**
+     * Unknown surface source type.
+     */
+    private final int SURFACE_TYPE_UNKNOWN = -1;
+
+    /**
+     * The surface is obtained from {@link android.view.SurfaceView}.
+     */
+    private final int SURFACE_TYPE_SURFACE_VIEW = 0;
+
+    /**
+     * The surface is obtained from {@link android.graphics.SurfaceTexture}.
+     */
+    private final int SURFACE_TYPE_SURFACE_TEXTURE = 1;
+
+    /**
      * Create a new {@link OutputConfiguration} instance with a {@link Surface},
      * with a surface group ID.
      *
@@ -179,12 +196,110 @@
         checkNotNull(surface, "Surface must not be null");
         checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
         mSurfaceGroupId = surfaceGroupId;
+        mSurfaceType = SURFACE_TYPE_UNKNOWN;
         mSurface = surface;
         mRotation = rotation;
         mConfiguredSize = SurfaceUtils.getSurfaceSize(surface);
         mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface);
         mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface);
         mConfiguredGenerationId = surface.getGenerationId();
+        mIsDeferredConfig = false;
+    }
+
+    /**
+     * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
+     * source class.
+     * <p>
+     * This constructor takes an argument for desired Surface size and the Surface source class
+     * without providing the actual output Surface. This is used to setup a output configuration
+     * with a deferred Surface. The application can use this output configuration to create a
+     * session.
+     * </p>
+     * <p>
+     * However, the actual output Surface must be set via {@link #setDeferredSurface} and finish the
+     * deferred Surface configuration via {@link CameraCaptureSession#finishDeferredConfiguration}
+     * before submitting a request with this Surface target. The deferred Surface can only be
+     * obtained from either from {@link android.view.SurfaceView} by calling
+     * {@link android.view.SurfaceHolder#getSurface}, or from
+     * {@link android.graphics.SurfaceTexture} via
+     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
+     * </p>
+     *
+     * @param surfaceSize Size for the deferred surface.
+     * @param klass a non-{@code null} {@link Class} object reference that indicates the source of
+     *            this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and
+     *            {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
+     * @hide
+     */
+    public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
+        checkNotNull(klass, "surfaceSize must not be null");
+        checkNotNull(klass, "klass must not be null");
+        if (klass == android.view.SurfaceHolder.class) {
+            mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
+        } else if (klass == android.graphics.SurfaceTexture.class) {
+            mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
+        } else {
+            mSurfaceType = SURFACE_TYPE_UNKNOWN;
+            throw new IllegalArgumentException("Unknow surface source class type");
+        }
+
+        mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
+        mSurface = null;
+        mRotation = ROTATION_0;
+        mConfiguredSize = surfaceSize;
+        mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
+        mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+        mConfiguredGenerationId = 0;
+        mIsDeferredConfig = true;
+    }
+
+    /**
+     * Check if this configuration has deferred configuration.
+     *
+     * <p>This will return true if the output configuration was constructed with surface deferred.
+     * It will return true even after the deferred surface is set later.</p>
+     *
+     * @return true if this configuration has deferred surface.
+     * @hide
+     */
+    public boolean isDeferredConfiguration() {
+        return mIsDeferredConfig;
+    }
+
+    /**
+     * Set the deferred surface to this OutputConfiguration.
+     *
+     * <p>
+     * The deferred surface must be obtained from either from {@link android.view.SurfaceView} by
+     * calling {@link android.view.SurfaceHolder#getSurface}, or from
+     * {@link android.graphics.SurfaceTexture} via
+     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). After the deferred
+     * surface is set, the application must finish the deferred surface configuration via
+     * {@link CameraCaptureSession#finishDeferredConfiguration} before submitting a request with
+     * this surface target.
+     * </p>
+     *
+     * @param surface The deferred surface to be set.
+     * @throws IllegalArgumentException if the Surface is invalid.
+     * @throws IllegalStateException if a Surface was already set to this deferred
+     *         OutputConfiguration.
+     * @hide
+     */
+    public void setDeferredSurface(@NonNull Surface surface) {
+        checkNotNull(surface, "Surface must not be null");
+        if (mSurface != null) {
+            throw new IllegalStateException("Deferred surface is already set!");
+        }
+
+        // This will throw IAE is the surface was abandoned.
+        Size surfaceSize = SurfaceUtils.getSurfaceSize(surface);
+        if (!surfaceSize.equals(mConfiguredSize)) {
+            Log.w(TAG, "Deferred surface size " + surfaceSize +
+                    " is different with pre-configured size " + mConfiguredSize +
+                    ", the pre-configured size will be used.");
+        }
+
+        mSurface = surface;
     }
 
     /**
@@ -203,10 +318,12 @@
         this.mSurface = other.mSurface;
         this.mRotation = other.mRotation;
         this.mSurfaceGroupId = other.mSurfaceGroupId;
+        this.mSurfaceType = other.mSurfaceType;
         this.mConfiguredDataspace = other.mConfiguredDataspace;
         this.mConfiguredFormat = other.mConfiguredFormat;
         this.mConfiguredSize = other.mConfiguredSize;
         this.mConfiguredGenerationId = other.mConfiguredGenerationId;
+        this.mIsDeferredConfig = other.mIsDeferredConfig;
     }
 
     /**
@@ -215,16 +332,30 @@
     private OutputConfiguration(@NonNull Parcel source) {
         int rotation = source.readInt();
         int surfaceSetId = source.readInt();
+        int surfaceType = source.readInt();
+        int width = source.readInt();
+        int height = source.readInt();
         Surface surface = Surface.CREATOR.createFromParcel(source);
-        checkNotNull(surface, "Surface must not be null");
         checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
         mSurfaceGroupId = surfaceSetId;
         mSurface = surface;
         mRotation = rotation;
-        mConfiguredSize = SurfaceUtils.getSurfaceSize(mSurface);
-        mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurface);
-        mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
-        mConfiguredGenerationId = mSurface.getGenerationId();
+        if (surface != null) {
+            mSurfaceType = SURFACE_TYPE_UNKNOWN;
+            mConfiguredSize = SurfaceUtils.getSurfaceSize(mSurface);
+            mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurface);
+            mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
+            mConfiguredGenerationId = mSurface.getGenerationId();
+            mIsDeferredConfig = true;
+        } else {
+            mSurfaceType = surfaceType;
+            mConfiguredSize = new Size(width, height);
+            mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
+            mConfiguredGenerationId = 0;
+            mConfiguredDataspace =
+                    StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+            mIsDeferredConfig = false;
+        }
     }
 
     /**
@@ -291,7 +422,12 @@
         }
         dest.writeInt(mRotation);
         dest.writeInt(mSurfaceGroupId);
-        mSurface.writeToParcel(dest, flags);
+        dest.writeInt(mSurfaceType);
+        dest.writeInt(mConfiguredSize.getWidth());
+        dest.writeInt(mConfiguredSize.getHeight());
+        if (mSurface != null) {
+            mSurface.writeToParcel(dest, flags);
+        }
     }
 
     /**
@@ -311,13 +447,20 @@
             return true;
         } else if (obj instanceof OutputConfiguration) {
             final OutputConfiguration other = (OutputConfiguration) obj;
+            boolean iSSurfaceEqual = mSurface == other.mSurface &&
+                    mConfiguredGenerationId == other.mConfiguredGenerationId ;
+            if (mIsDeferredConfig) {
+                Log.i(TAG, "deferred config has the same surface");
+                iSSurfaceEqual = true;
+            }
             return mRotation == other.mRotation &&
-                   mSurface == other.mSurface &&
-                   mConfiguredGenerationId == other.mConfiguredGenerationId &&
+                   iSSurfaceEqual&&
                    mConfiguredSize.equals(other.mConfiguredSize) &&
                    mConfiguredFormat == other.mConfiguredFormat &&
                    mConfiguredDataspace == other.mConfiguredDataspace &&
-                   mSurfaceGroupId == other.mSurfaceGroupId;
+                   mSurfaceGroupId == other.mSurfaceGroupId &&
+                   mSurfaceType == other.mSurfaceType &&
+                   mIsDeferredConfig == other.mIsDeferredConfig;
         }
         return false;
     }
@@ -327,15 +470,26 @@
      */
     @Override
     public int hashCode() {
+        // Need ensure that the hashcode remains unchanged after set a deferred surface. Otherwise
+        // The deferred output configuration will be lost in the camera streammap after the deferred
+        // surface is set.
+        if (mIsDeferredConfig) {
+            return HashCodeHelpers.hashCode(
+                    mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
+                    mSurfaceGroupId, mSurfaceType);
+        }
+
         return HashCodeHelpers.hashCode(
             mRotation, mSurface.hashCode(), mConfiguredGenerationId,
             mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId);
     }
 
     private static final String TAG = "OutputConfiguration";
-    private final Surface mSurface;
+    private Surface mSurface;
     private final int mRotation;
-    private int mSurfaceGroupId;
+    private final int mSurfaceGroupId;
+    // Surface source type, this is only used by the deferred surface configuration objects.
+    private final int mSurfaceType;
 
     // The size, format, and dataspace of the surface when OutputConfiguration is created.
     private final Size mConfiguredSize;
@@ -343,4 +497,6 @@
     private final int mConfiguredDataspace;
     // Surface generation ID to distinguish changes to Surface native internals
     private final int mConfiguredGenerationId;
+    // Flag indicating if this config has deferred surface.
+    private final boolean mIsDeferredConfig;
 }
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index f9a7d19..8f21b38 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -21,6 +21,7 @@
 
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -476,6 +477,26 @@
     }
 
     /**
+     * Grants permission to specified package for USB device without showing system dialog.
+     * Only system components can call this function, as it requires the MANAGE_USB permission.
+     * @param device to request permissions for
+     * @param packageName of package to grant permissions
+     *
+     * {@hide}
+     */
+    public void grantPermission(UsbDevice device, String packageName) {
+        try {
+            int uid = mContext.getPackageManager()
+                .getPackageUidAsUser(packageName, mContext.getUserId());
+            mService.grantDevicePermission(device, uid);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Package " + packageName + " not found.", e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns true if the specified USB function is currently enabled when in device mode.
      * <p>
      * USB functions represent interfaces which are published to the host to access
diff --git a/core/java/android/net/ConnectivityMetricsLogger.java b/core/java/android/net/ConnectivityMetricsLogger.java
index d8cdde9..7373495 100644
--- a/core/java/android/net/ConnectivityMetricsLogger.java
+++ b/core/java/android/net/ConnectivityMetricsLogger.java
@@ -33,28 +33,39 @@
 
     // Component Tags
     public static final int COMPONENT_TAG_CONNECTIVITY = 0;
-    public static final int COMPONENT_TAG_BLUETOOTH = 1;
-    public static final int COMPONENT_TAG_WIFI = 2;
-    public static final int COMPONENT_TAG_TELECOM = 3;
-    public static final int COMPONENT_TAG_TELEPHONY = 4;
-
-    public static final int NUMBER_OF_COMPONENTS = 5;
+    public static final int COMPONENT_TAG_BLUETOOTH    = 1;
+    public static final int COMPONENT_TAG_WIFI         = 2;
+    public static final int COMPONENT_TAG_TELECOM      = 3;
+    public static final int COMPONENT_TAG_TELEPHONY    = 4;
+    public static final int NUMBER_OF_COMPONENTS       = 5;
 
     // Event Tag
     public static final int TAG_SKIPPED_EVENTS = -1;
 
     public static final String DATA_KEY_EVENTS_COUNT = "count";
 
-    private IConnectivityMetricsLogger mService;
-
-    private long mServiceUnblockedTimestampMillis = 0;
-    private int mNumSkippedEvents = 0;
+    /** {@hide} */ protected final IConnectivityMetricsLogger mService;
+    /** {@hide} */ protected volatile long mServiceUnblockedTimestampMillis;
+    private int mNumSkippedEvents;
 
     public ConnectivityMetricsLogger() {
         mService = IConnectivityMetricsLogger.Stub.asInterface(ServiceManager.getService(
                 CONNECTIVITY_METRICS_LOGGER_SERVICE));
     }
 
+    /**
+     * Log a ConnectivityMetricsEvent.
+     *
+     * This method keeps track of skipped events when MetricsLoggerService throttles input events.
+     * It skips logging when MetricsLoggerService is active. When throttling ends, it logs a
+     * meta-event containing the number of events dropped. It is not safe to call this method
+     * concurrently from different threads.
+     *
+     * @param timestamp is the epoch timestamp of the event in ms.
+     * @param componentTag is the COMPONENT_* constant the event belongs to.
+     * @param eventTag is an event type constant whose meaning is specific to the component tag.
+     * @param data is a Parcelable instance representing the event.
+     */
     public void logEvent(long timestamp, int componentTag, int eventTag, Parcelable data) {
         if (mService == null) {
             if (DBG) {
@@ -104,7 +115,7 @@
                 }
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "Error logging event " + e.getMessage());
+            Log.e(TAG, "Error logging event", e);
         }
     }
 
@@ -121,8 +132,8 @@
     public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
         try {
             return mService.getEvents(reference);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "IConnectivityMetricsLogger.getEvents: " + ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "IConnectivityMetricsLogger.getEvents", e);
             return null;
         }
     }
@@ -133,8 +144,8 @@
     public boolean register(PendingIntent newEventsIntent) {
         try {
             return mService.register(newEventsIntent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "IConnectivityMetricsLogger.register: " + ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "IConnectivityMetricsLogger.register", e);
             return false;
         }
     }
@@ -142,8 +153,8 @@
     public boolean unregister(PendingIntent newEventsIntent) {
         try {
             mService.unregister(newEventsIntent);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "IConnectivityMetricsLogger.unregister: " + ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "IConnectivityMetricsLogger.unregister", e);
             return false;
         }
 
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 5511a24..69f50a2 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -52,6 +52,15 @@
     public boolean acceptUnvalidated;
 
     /**
+     * Set to avoid surfacing the "Sign in to network" notification.
+     * if carrier receivers/apps are registered to handle the carrier-specific provisioning
+     * procedure, a carrier specific provisioning notification will be placed.
+     * only one notification should be displayed. This field is set based on
+     * which notification should be used for provisioning.
+     */
+    public boolean provisioningNotificationDisabled;
+
+    /**
      * For mobile networks, this is the subscriber ID (such as IMSI).
      */
     public String subscriberId;
@@ -65,6 +74,7 @@
             explicitlySelected = nm.explicitlySelected;
             acceptUnvalidated = nm.acceptUnvalidated;
             subscriberId = nm.subscriberId;
+            provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
         }
     }
 
@@ -79,6 +89,7 @@
         out.writeInt(explicitlySelected ? 1 : 0);
         out.writeInt(acceptUnvalidated ? 1 : 0);
         out.writeString(subscriberId);
+        out.writeInt(provisioningNotificationDisabled ? 1 : 0);
     }
 
     public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
@@ -89,6 +100,7 @@
             networkMisc.explicitlySelected = in.readInt() != 0;
             networkMisc.acceptUnvalidated = in.readInt() != 0;
             networkMisc.subscriberId = in.readString();
+            networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
             return networkMisc;
         }
 
diff --git a/core/java/android/net/metrics/IpConnectivityEvent.java b/core/java/android/net/metrics/IpConnectivityEvent.java
index 95576c2..d3771dc 100644
--- a/core/java/android/net/metrics/IpConnectivityEvent.java
+++ b/core/java/android/net/metrics/IpConnectivityEvent.java
@@ -16,20 +16,13 @@
 
 package android.net.metrics;
 
-import android.net.ConnectivityMetricsLogger;
-import android.os.Parcel;
 import android.os.Parcelable;
 
-/**
- * {@hide}
- */
+/** {@hide} */
 public abstract class IpConnectivityEvent {
-    private static final int COMPONENT_TAG = ConnectivityMetricsLogger.COMPONENT_TAG_CONNECTIVITY;
+    private static final IpConnectivityLog sMetricsLog = new IpConnectivityLog();
 
-    private static final ConnectivityMetricsLogger sMetricsLogger = new ConnectivityMetricsLogger();
-
-    public static <T extends IpConnectivityEvent & Parcelable> void logEvent(T event) {
-        // TODO: consider using different component for DNS event.
-        sMetricsLogger.logEvent(System.currentTimeMillis(), COMPONENT_TAG, 0, event);
+    static <T extends IpConnectivityEvent & Parcelable> void logEvent(T event) {
+        sMetricsLog.log(System.currentTimeMillis(), event);
     }
-};
+}
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
new file mode 100644
index 0000000..233ff74
--- /dev/null
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.net.metrics;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.ConnectivityMetricsLogger;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Specialization of the ConnectivityMetricsLogger class for recording IP connectivity events.
+ * {@hide}
+ */
+class IpConnectivityLog extends ConnectivityMetricsLogger {
+    private static String TAG = "IpConnectivityMetricsLogger";
+    private static final boolean DBG = false;
+
+    /**
+     * Log an IpConnectivity event. Contrary to logEvent(), this method does not
+     * keep track of skipped events and is thread-safe for callers.
+     *
+     * @param timestamp is the epoch timestamp of the event in ms.
+     * @param data is a Parcelable IpConnectivityEvent instance representing the event.
+     *
+     * @return true if the event was successfully logged.
+     */
+    public <T extends IpConnectivityEvent & Parcelable> boolean log(long timestamp, T data) {
+        if (mService == null) {
+            if (DBG) {
+                Log.d(TAG, CONNECTIVITY_METRICS_LOGGER_SERVICE + " service not ready");
+            }
+            return false;
+        }
+
+        if (System.currentTimeMillis() < mServiceUnblockedTimestampMillis) {
+            if (DBG) {
+                Log.d(TAG, "skipping logging due to throttling for IpConnectivity component");
+            }
+            return false;
+        }
+
+        try {
+            final ConnectivityMetricsEvent event =
+                new ConnectivityMetricsEvent(timestamp, COMPONENT_TAG_CONNECTIVITY, 0, data);
+            final long result = mService.logEvent(event);
+            if (result >= 0) {
+                mServiceUnblockedTimestampMillis = result;
+            }
+            return (result == 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error logging event", e);
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 41ff9fd..783c25a 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -290,6 +290,7 @@
 
     // Guarded by NfcAdapter.class
     static boolean sIsInitialized = false;
+    static boolean sHasNfcFeature;
 
     // Final after first constructor, except for
     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -435,42 +436,65 @@
     }
 
     /**
+     * Helper to check if this device is NFC HCE capable, by checking for
+     * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * but without using a context.
+     */
+    private static boolean hasNfcHceFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
+                || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+            return false;
+        }
+    }
+
+    /**
      * Returns the NfcAdapter for application context,
      * or throws if NFC is not available.
      * @hide
      */
     public static synchronized NfcAdapter getNfcAdapter(Context context) {
         if (!sIsInitialized) {
+            sHasNfcFeature = hasNfcFeature();
+            boolean hasHceFeature = hasNfcHceFeature();
             /* is this device meant to have NFC */
-            if (!hasNfcFeature()) {
+            if (!sHasNfcFeature && !hasHceFeature) {
                 Log.v(TAG, "this device does not have NFC support");
                 throw new UnsupportedOperationException();
             }
-
             sService = getServiceInterface();
             if (sService == null) {
                 Log.e(TAG, "could not retrieve NFC service");
                 throw new UnsupportedOperationException();
             }
-            try {
-                sTagService = sService.getNfcTagInterface();
-            } catch (RemoteException e) {
-                Log.e(TAG, "could not retrieve NFC Tag service");
-                throw new UnsupportedOperationException();
+            if (sHasNfcFeature) {
+                try {
+                    sTagService = sService.getNfcTagInterface();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "could not retrieve NFC Tag service");
+                    throw new UnsupportedOperationException();
+                }
             }
-
-            try {
-                sCardEmulationService = sService.getNfcCardEmulationInterface();
-            } catch (RemoteException e) {
-                Log.e(TAG, "could not retrieve card emulation service");
-                throw new UnsupportedOperationException();
-            }
-
-            try {
-                sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
-            } catch (RemoteException e) {
-                Log.e(TAG, "could not retrieve NFC-F card emulation service");
-                throw new UnsupportedOperationException();
+            if (hasHceFeature) {
+                try {
+                    sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "could not retrieve NFC-F card emulation service");
+                    throw new UnsupportedOperationException();
+                }
+                try {
+                    sCardEmulationService = sService.getNfcCardEmulationInterface();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "could not retrieve card emulation service");
+                    throw new UnsupportedOperationException();
+                }
             }
 
             sIsInitialized = true;
@@ -838,8 +862,14 @@
      *
      * @param uris an array of Uri(s) to push over Android Beam
      * @param activity activity for which the Uri(s) will be pushed
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void setBeamPushUris(Uri[] uris, Activity activity) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         if (activity == null) {
             throw new NullPointerException("activity cannot be null");
         }
@@ -914,8 +944,14 @@
      *
      * @param callback callback, or null to disable
      * @param activity activity for which the Uri(s) will be pushed
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         if (activity == null) {
             throw new NullPointerException("activity cannot be null");
         }
@@ -992,9 +1028,15 @@
      * @param activities optional additional activities, however we strongly recommend
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void setNdefPushMessage(NdefMessage message, Activity activity,
             Activity ... activities) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         int targetSdkVersion = getSdkVersion();
         try {
             if (activity == null) {
@@ -1024,6 +1066,11 @@
      */
     @SystemApi
     public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         if (activity == null) {
             throw new NullPointerException("activity cannot be null");
         }
@@ -1094,9 +1141,15 @@
      * @param activities optional additional activities, however we strongly recommend
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
             Activity ... activities) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         int targetSdkVersion = getSdkVersion();
         try {
             if (activity == null) {
@@ -1168,9 +1221,15 @@
      * @param activities optional additional activities, however we strongly recommend
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
             Activity activity, Activity ... activities) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         int targetSdkVersion = getSdkVersion();
         try {
             if (activity == null) {
@@ -1227,9 +1286,15 @@
      * @param techLists the tech lists used to perform matching for dispatching of the
      *      {@link NfcAdapter#ACTION_TECH_DISCOVERED} intent
      * @throws IllegalStateException if the Activity is not currently in the foreground
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void enableForegroundDispatch(Activity activity, PendingIntent intent,
             IntentFilter[] filters, String[][] techLists) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         if (activity == null || intent == null) {
             throw new NullPointerException();
         }
@@ -1263,8 +1328,14 @@
      *
      * @param activity the Activity to disable dispatch to
      * @throws IllegalStateException if the Activity has already been paused
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void disableForegroundDispatch(Activity activity) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
                 mForegroundDispatchListener);
         disableForegroundDispatchInternal(activity, false);
@@ -1309,9 +1380,15 @@
      * @param callback the callback to be called when a tag is discovered
      * @param flags Flags indicating poll technologies and other optional parameters
      * @param extras Additional extras for configuring reader mode.
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
             Bundle extras) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         mNfcActivityManager.enableReaderMode(activity, callback, flags, extras);
     }
 
@@ -1321,8 +1398,14 @@
      * all supported tag technologies.
      *
      * @param activity the Activity that currently has reader mode enabled
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void disableReaderMode(Activity activity) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         mNfcActivityManager.disableReaderMode(activity);
     }
 
@@ -1349,8 +1432,14 @@
      *
      * @param activity the current foreground Activity that has registered data to share
      * @return whether the Beam animation was successfully invoked
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public boolean invokeBeam(Activity activity) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         if (activity == null) {
             throw new NullPointerException("activity may not be null.");
         }
@@ -1404,10 +1493,16 @@
      * @param activity foreground activity
      * @param message a NDEF Message to push over NFC
      * @throws IllegalStateException if the activity is not currently in the foreground
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      * @deprecated use {@link #setNdefPushMessage} instead
      */
     @Deprecated
     public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         if (activity == null || message == null) {
             throw new NullPointerException();
         }
@@ -1432,10 +1527,16 @@
      *
      * @param activity the Foreground activity
      * @throws IllegalStateException if the Activity has already been paused
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      * @deprecated use {@link #setNdefPushMessage} instead
      */
     @Deprecated
     public void disableForegroundNdefPush(Activity activity) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         if (activity == null) {
             throw new NullPointerException();
         }
@@ -1452,6 +1553,9 @@
      */
     @SystemApi
     public boolean enableNdefPush() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
         try {
             return sService.enableNdefPush();
         } catch (RemoteException e) {
@@ -1467,6 +1571,11 @@
      */
     @SystemApi
     public boolean disableNdefPush() {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         try {
             return sService.disableNdefPush();
         } catch (RemoteException e) {
@@ -1497,8 +1606,14 @@
      *
      * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
      * @return true if NDEF Push feature is enabled
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public boolean isNdefPushEnabled() {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         try {
             return sService.isNdefPushEnabled();
         } catch (RemoteException e) {
@@ -1623,6 +1738,11 @@
     @SystemApi
     public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
                                        String[] tagTechnologies) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         // If there are no tag technologies, don't bother adding unlock handler
         if (tagTechnologies.length == 0) {
             return false;
@@ -1666,6 +1786,11 @@
      */
     @SystemApi
     public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
         try {
             synchronized (mLock) {
                 if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index d8be2b6..80927f3 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -340,6 +340,33 @@
     }
 
     /**
+     * Return preloads directory.
+     * <p>This directory may contain pre-loaded content such as
+     * {@link #getDataPreloadsDemoDirectory() demo videos} and
+     * {@link #getDataPreloadsAppsDirectory() APK files} .
+     * {@hide}
+     */
+    public static File getDataPreloadsDirectory() {
+        return new File(getDataDirectory(), "preloads");
+    }
+
+    /**
+     * @see #getDataPreloadsDirectory()
+     * {@hide}
+     */
+    public static File getDataPreloadsDemoDirectory() {
+        return new File(getDataPreloadsDirectory(), "demo");
+    }
+
+    /**
+     * @see #getDataPreloadsDirectory()
+     * {@hide}
+     */
+    public static File getDataPreloadsAppsDirectory() {
+        return new File(getDataPreloadsDirectory(), "apps");
+    }
+
+    /**
      * Return the primary shared/external storage directory. This directory may
      * not currently be accessible if it has been mounted by the user on their
      * computer, has been removed from the device, or some other problem has
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index b3cf710..9bbe8f9 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -57,14 +57,19 @@
     /**
      * Power hint:
      * Interaction: The user is interacting with the device. The corresponding data field must be
-     * the expected duration of the fling, or 0 if unknown.
+     * the expected duration of the interaction, or 0 if unknown.
      *
-     * Sustained Performance Mode: Enable/Disables Sustained Performance Mode.
+     * Sustained Performance Mode: The corresponding data field must be Enable/Disable
+     * Sustained Performance Mode.
+     *
+     * Launch: This is specific for activity launching. The corresponding data field must be
+     * the expected duration of the required boost, or 0 if unknown.
      *
      * These must be kept in sync with the values in hardware/libhardware/include/hardware/power.h
      */
     public static final int POWER_HINT_INTERACTION = 2;
     public static final int POWER_HINT_SUSTAINED_PERFORMANCE_MODE = 6;
+    public static final int POWER_HINT_LAUNCH = 8;
 
     public static String wakefulnessToString(int wakefulness) {
         switch (wakefulness) {
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index dd7be53..507379b 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -570,18 +570,19 @@
      * @throws SecurityException if the current user is not allowed to wipe data.
      */
     public static void rebootWipeUserData(Context context) throws IOException {
-        rebootWipeUserData(context, false, context.getPackageName());
+        rebootWipeUserData(context, false /* shutdown */, context.getPackageName(),
+                false /* force */);
     }
 
     /** {@hide} */
     public static void rebootWipeUserData(Context context, String reason) throws IOException {
-        rebootWipeUserData(context, false, reason);
+        rebootWipeUserData(context, false /* shutdown */, reason, false /* force */);
     }
 
     /** {@hide} */
     public static void rebootWipeUserData(Context context, boolean shutdown)
             throws IOException {
-        rebootWipeUserData(context, shutdown, context.getPackageName());
+        rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */);
     }
 
     /**
@@ -595,6 +596,9 @@
      * @param shutdown  if true, the device will be powered down after
      *                  the wipe completes, rather than being rebooted
      *                  back to the regular system.
+     * @param reason    the reason for the wipe that is visible in the logs
+     * @param force     whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction
+     *                  should be ignored
      *
      * @throws IOException  if writing the recovery command file
      * fails, or if the reboot itself fails.
@@ -602,10 +606,10 @@
      *
      * @hide
      */
-    public static void rebootWipeUserData(Context context, boolean shutdown, String reason)
-            throws IOException {
+    public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
+            boolean force) throws IOException {
         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        if (um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
+        if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
             throw new SecurityException("Wiping data is not allowed for this user.");
         }
         final ConditionVariable condition = new ConditionVariable();
@@ -658,6 +662,31 @@
     }
 
     /**
+     * Reboot into recovery and wipe the A/B device.
+     *
+     * @param Context      the Context to use.
+     * @param packageFile  the wipe package to be applied.
+     * @param reason       the reason to wipe.
+     *
+     * @throws IOException if something goes wrong.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void rebootWipeAb(Context context, File packageFile, String reason)
+            throws IOException {
+        String reasonArg = null;
+        if (!TextUtils.isEmpty(reason)) {
+            reasonArg = "--reason=" + sanitizeArg(reason);
+        }
+
+        final String filename = packageFile.getCanonicalPath();
+        final String filenameArg = "--wipe_package=" + filename;
+        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg);
+    }
+
+    /**
      * Reboot into the recovery system with the supplied argument.
      * @param args to pass to the recovery utility.
      * @throws IOException if something goes wrong.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bcc8d46..d742206 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -789,7 +789,7 @@
      */
     public boolean isPrimaryUser() {
         UserInfo user = getUserInfo(UserHandle.myUserId());
-        return user != null ? user.isPrimary() : false;
+        return user != null && user.isPrimary();
     }
 
     /**
@@ -855,7 +855,19 @@
      */
     public boolean isGuestUser() {
         UserInfo user = getUserInfo(UserHandle.myUserId());
-        return user != null ? user.isGuest() : false;
+        return user != null && user.isGuest();
+    }
+
+    /**
+     * Checks if the calling app is running in a demo user.
+     * <p>
+     * Caller must hold the MANAGE_USERS permission.
+     * @return whether the caller is a demo user.
+     * @hide
+     */
+    public boolean isDemoUser() {
+        UserInfo user = getUserInfo(UserHandle.myUserId());
+        return user != null && user.isDemo();
     }
 
     /**
@@ -1975,6 +1987,10 @@
         if (!supportsMultipleUsers()) {
             return false;
         }
+        // If Demo Mode is on, don't show user switcher
+        if (isDeviceInDemoMode(mContext)) {
+            return false;
+        }
         List<UserInfo> users = getUsers(true);
         if (users == null) {
            return false;
@@ -1991,6 +2007,14 @@
     }
 
     /**
+     * @hide
+     */
+    public static boolean isDeviceInDemoMode(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
+    }
+
+    /**
      * Returns a serial number on this device for a given userHandle. User handles can be recycled
      * when deleting and creating users, but serial numbers are not reused until the device is wiped.
      * @param userHandle
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 485bbd1..968996f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -45,8 +45,11 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -115,6 +118,10 @@
     public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
 
     private static volatile IMountService sMountService = null;
+    private static final String INTERNAL_STORAGE_SIZE_PATH =
+            "/sys/block/mmcblk0/size";
+    private static final String INTERNAL_STORAGE_SECTOR_SIZE =
+            "/sys/block/mmcblk0/queue/hw_sector_size";
 
     private final Context mContext;
     private final ContentResolver mResolver;
@@ -903,6 +910,23 @@
         return getVolumeList(UserHandle.myUserId(), FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE)[0];
     }
 
+    /** {@hide} */
+    public long getPrimaryStorageSize() {
+        final long numberBlocks = readLong(INTERNAL_STORAGE_SIZE_PATH);
+        final long sectorSize = readLong(INTERNAL_STORAGE_SECTOR_SIZE);
+        return numberBlocks * sectorSize;
+    }
+
+    private long readLong(String path) {
+        try (final FileInputStream fis = new FileInputStream(path);
+                final BufferedReader reader = new BufferedReader(new InputStreamReader(fis));) {
+            return Long.parseLong(reader.readLine());
+        } catch (Exception e) {
+            Slog.w("Could not read " + path, e);
+            return 0;
+        }
+    }
+
     /** @removed */
     public @NonNull StorageVolume[] getVolumeList() {
         return getVolumeList(mContext.getUserId(), 0);
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 893eb37..fbd61cf3 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -179,6 +179,7 @@
          * <li>{@link #VOICEMAIL_TYPE}</li>
          * <li>{@link #REJECTED_TYPE}</li>
          * <li>{@link #BLOCKED_TYPE}</li>
+         * <li>{@link #ANSWERED_EXTERNALLY_TYPE}</li>
          * </ul>
          * </p>
          */
@@ -200,7 +201,6 @@
          * Call log type for a call which was answered on another device.  Used in situations where
          * a call rings on multiple devices simultaneously and it ended up being answered on a
          * device other than the current one.
-         * @hide
          */
         public static final int ANSWERED_EXTERNALLY_TYPE = 7;
 
@@ -214,10 +214,7 @@
         /** Call had video. */
         public static final int FEATURES_VIDEO = 0x1;
 
-        /**
-         * Call was pulled externally.
-         * @hide
-         */
+        /** Call was pulled externally. */
         public static final int FEATURES_PULLED_EXTERNALLY = 0x2;
 
         /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3d37256..5853503 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1277,6 +1277,17 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
 
+    /**
+     * Activity Action: Show the Deletion Helper settings.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DELETION_HELPER_SETTINGS
+            = "android.settings.DELETION_HELPER_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
@@ -6227,6 +6238,29 @@
                 "managed_profile_contact_remote_search";
 
         /**
+         * Whether or not the automatic storage manager is enabled and should run on the device.
+         *
+         * @hide
+         */
+        public static final String AUTOMATIC_STORAGE_MANAGER_ENABLED =
+                "automatic_storage_manager_enabled";
+
+        /**
+         * How many days of information for the automatic storage manager to retain on the device.
+         *
+         * @hide
+         */
+        public static final String AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN =
+                "automatic_storage_manager_days_to_retain";
+
+        /**
+         * Default number of days of information for the automatic storage manager to retain.
+         *
+         * @hide
+         */
+        public static final int AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_DEFAULT = 90;
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -8549,6 +8583,24 @@
                 "ephemeral_cookie_max_size_bytes";
 
         /**
+         * A mask applied to the ephemeral hash to generate the hash prefix.
+         * <p>
+         * Type: int
+         *
+         * @hide
+         */
+        public static final String EPHEMERAL_HASH_PREFIX_MASK = "ephemeral_hash_prefix_mask";
+
+        /**
+         * Number of hash prefixes to send during ephemeral resolution.
+         * <p>
+         * Type: int
+         *
+         * @hide
+         */
+        public static final String EPHEMERAL_HASH_PREFIX_COUNT = "ephemeral_hash_prefix_count";
+
+        /**
          * The duration for caching uninstalled ephemeral apps.
          * <p>
          * Type: long
@@ -8584,6 +8636,15 @@
         public static final String SAFE_BOOT_DISALLOWED = "safe_boot_disallowed";
 
         /**
+         * Whether this device is currently in retail demo mode. If true, device
+         * usage is severely limited.
+         * <p>
+         * Type: int (0 for false, 1 for true)
+         * @hide
+         */
+        public static final String DEVICE_DEMO_MODE = "device_demo_mode";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *
@@ -8982,12 +9043,28 @@
         public static final String ENABLE_CELLULAR_ON_BOOT = "enable_cellular_on_boot";
 
         /**
+         * Whether toggling OEM unlock is disallowed. If disallowed, it is not possible to enable or
+         * disable OEM unlock.
+         * <p>
+         * Type: int (0: allow OEM unlock setting. 1: disallow OEM unlock)
+         * @hide
+         */
+        public static final String OEM_UNLOCK_DISALLOWED = "oem_unlock_disallowed";
+
+        /**
          * The maximum allowed notification enqueue rate in Hertz.
          *
          * Should be a float, and includes both posts and updates.
          * @hide
          */
         public static final String MAX_NOTIFICATION_ENQUEUE_RATE = "max_notification_enqueue_rate";
+
+        /**
+         * Whether SystemUI navigation keys is enabled.
+         * @hide
+         */
+        public static final String SYSTEM_NAVIGATION_KEYS_ENABLED =
+                "system_navigation_keys_enabled";
     }
 
     /**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 6a3cc02..ab9e497 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
-import android.database.Cursor;
 import android.net.Uri;
 import android.provider.CallLog.Calls;
 import android.telecom.PhoneAccount;
@@ -107,6 +106,47 @@
     public static final String ACTION_SYNC_VOICEMAIL = "android.provider.action.SYNC_VOICEMAIL";
 
     /**
+     * Broadcast intent to inform a new visual voicemail SMS has been received. This intent will
+     * only be delivered to the voicemail client. The intent will have the following extra values:
+     *
+     * <ul>
+     *   <li><em>{@link #EXTRA_VOICEMAIL_SMS_TYPE}</em> - (String) The event type of the SMS. Common
+     *   values are "SYNC" or "STATUS"</li>
+     *   <li><em>{@link #EXTRA_VOICEMAIL_SMS_DATA}</em> - (Bundle) The fields sent by the SMS</li>
+     *   <li><em>{@link #EXTRA_VOICEMAIL_SMS_SUBID}</em> - (Integer) The subscription ID of the
+     *   phone account that received the SMS</li>
+     * </ul>
+     */
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VOICEMAIL_SMS_RECEIVED =
+            "android.intent.action.VOICEMAIL_SMS_RECEIVED";
+
+    /**
+     * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate the
+     * event type of the SMS. Common values are "SYNC" or "STATUS"
+     */
+    /** @hide */
+    public static final String EXTRA_VOICEMAIL_SMS_PREFIX =
+            "com.android.voicemail.extra.VOICEMAIL_SMS_PREFIX";
+
+    /**
+     * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate the
+     * fields sent by the SMS
+     */
+    /** @hide */
+    public static final String EXTRA_VOICEMAIL_SMS_FIELDS =
+            "com.android.voicemail.extra.VOICEMAIL_SMS_FIELDS";
+
+    /**
+     * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate he
+     * subscription ID of the phone account that received the SMS.
+     */
+    /** @hide */
+    public static final String EXTRA_VOICEMAIL_SMS_SUBID =
+            "com.android.voicemail.extra.VOICEMAIL_SMS_SUBID";
+
+    /**
      * Extra included in {@link Intent#ACTION_PROVIDER_CHANGED} broadcast intents to indicate if the
      * receiving package made this change.
      */
@@ -360,6 +400,20 @@
          */
         public static final String SOURCE_PACKAGE = SOURCE_PACKAGE_FIELD;
 
+        /**
+         * The type of the source, which determines how to interpret source-specific states.
+         * Typically this will be set to the same string as
+         * {@link android.telephony.CarrierConfigManager#KEY_VVM_TYPE_STRING}. For example,
+         * "vvm_type_omtp".
+         *
+         * <P>Type: TEXT</P>
+         *
+         * @see #CONFIGURATION_STATE
+         * @see #DATA_CHANNEL_STATE
+         * @see #NOTIFICATION_CHANNEL_STATE
+         */
+        public static final String SOURCE_TYPE = "source_type";
+
         // Note: Multiple entries may exist for a single source if they are differentiated by the
         // PHONE_ACCOUNT_* fields.
 
@@ -392,6 +446,10 @@
         public static final String VOICEMAIL_ACCESS_URI = "voicemail_access_uri";
         /**
          * The configuration state of the voicemail source.
+         *
+         * <P>Negative values are reserved to the source for source-specific states, see
+         * {@link #SOURCE_TYPE}
+         *
          * <P> Possible values:
          * {@link #CONFIGURATION_STATE_OK},
          * {@link #CONFIGURATION_STATE_NOT_CONFIGURED},
@@ -399,14 +457,7 @@
          * <P>Type: INTEGER</P>
          */
         public static final String CONFIGURATION_STATE = "configuration_state";
-        /**
-         * Value of {@link #CONFIGURATION_STATE} passed into
-         * {@link #setStatus(Context, PhoneAccountHandle, int, int, int)} to indicate that the
-         * {@link #CONFIGURATION_STATE} field is not to be changed
-         *
-         * @hide
-         */
-        public static final int CONFIGURATION_STATE_IGNORE = -1;
+
         /** Value of {@link #CONFIGURATION_STATE} to indicate an all OK configuration status. */
         public static final int CONFIGURATION_STATE_OK = 0;
         /**
@@ -424,6 +475,10 @@
         /**
          * The data channel state of the voicemail source. This the channel through which the source
          * pulls voicemail data from a remote server.
+         *
+         * <P>Negative values are reserved to the source for source-specific states, see
+         * {@link #SOURCE_TYPE}
+         *
          * <P> Possible values:
          * {@link #DATA_CHANNEL_STATE_OK},
          * {@link #DATA_CHANNEL_STATE_NO_CONNECTION}
@@ -431,14 +486,7 @@
          * <P>Type: INTEGER</P>
          */
         public static final String DATA_CHANNEL_STATE = "data_channel_state";
-        /**
-         * Value of {@link #DATA_CHANNEL_STATE} passed into
-         * {@link #setStatus(Context, PhoneAccountHandle, int, int, int)} to indicate that the
-         * {@link #DATA_CHANNEL_STATE} field is not to be changed
-         *
-         * @hide
-         */
-        public static final int DATA_CHANNEL_STATE_IGNORE = -1;
+
         /**
          *  Value of {@link #DATA_CHANNEL_STATE} to indicate that data channel is working fine.
          */
@@ -478,6 +526,10 @@
         /**
          * The notification channel state of the voicemail source. This is the channel through which
          * the source gets notified of new voicemails on the remote server.
+         *
+         * <P>Negative values are reserved to the source for source-specific states, see
+         * {@link #SOURCE_TYPE}
+         *
          * <P> Possible values:
          * {@link #NOTIFICATION_CHANNEL_STATE_OK},
          * {@link #NOTIFICATION_CHANNEL_STATE_NO_CONNECTION},
@@ -486,14 +538,7 @@
          * <P>Type: INTEGER</P>
          */
         public static final String NOTIFICATION_CHANNEL_STATE = "notification_channel_state";
-        /**
-         * Value of {@link #NOTIFICATION_CHANNEL_STATE} passed into
-         * {@link #setStatus(Context, PhoneAccountHandle, int, int, int)} to indicate that the
-         * {@link #NOTIFICATION_CHANNEL_STATE} field is not to be changed
-         *
-         * @hide
-         */
-        public static final int NOTIFICATION_CHANNEL_STATE_IGNORE = -1;
+
         /**
          * Value of {@link #NOTIFICATION_CHANNEL_STATE} to indicate that the notification channel is
          * working fine.
@@ -543,67 +588,5 @@
             return Status.CONTENT_URI.buildUpon()
                     .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build();
         }
-
-        /**
-         * A helper method to set the status of a voicemail source.
-         *
-         * @param context The context from the package calling the method. This will be the source.
-         * @param accountHandle The handle for the account the source is associated with.
-         * @param configurationState See {@link Status#CONFIGURATION_STATE}
-         * @param dataChannelState See {@link Status#DATA_CHANNEL_STATE}
-         * @param notificationChannelState See {@link Status#NOTIFICATION_CHANNEL_STATE}
-         *
-         * @hide
-         */
-        public static void setStatus(Context context, PhoneAccountHandle accountHandle,
-                int configurationState, int dataChannelState, int notificationChannelState) {
-            ContentValues values = new ContentValues();
-            values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME,
-                    accountHandle.getComponentName().flattenToString());
-            values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId());
-            if(configurationState != CONFIGURATION_STATE_IGNORE) {
-                values.put(Status.CONFIGURATION_STATE, configurationState);
-            }
-            if(dataChannelState != DATA_CHANNEL_STATE_IGNORE) {
-                values.put(Status.DATA_CHANNEL_STATE, dataChannelState);
-            }
-            if(notificationChannelState != NOTIFICATION_CHANNEL_STATE_IGNORE) {
-                values.put(Status.NOTIFICATION_CHANNEL_STATE, notificationChannelState);
-            }
-            ContentResolver contentResolver = context.getContentResolver();
-            Uri statusUri = buildSourceUri(context.getPackageName());
-            contentResolver.insert(statusUri, values);
-        }
-
-        /**
-         * A helper method to set the quota of a voicemail source. Unit is unspecified.
-         *
-         * @param context The context from the package calling the method. This will be the source.
-         * @param accountHandle The handle for the account the source is associated with.
-         * @param occupied See {@link Status#QUOTA_OCCUPIED}
-         * @param total See {@link Status#QUOTA_TOTAL}
-         *
-         * @hide
-         */
-        public static void setQuota(Context context, PhoneAccountHandle accountHandle, int occupied,
-                int total) {
-            if (occupied == QUOTA_UNAVAILABLE && total == QUOTA_UNAVAILABLE) {
-                return;
-            }
-            ContentValues values = new ContentValues();
-            values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME,
-                    accountHandle.getComponentName().flattenToString());
-            values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId());
-            if (occupied != QUOTA_UNAVAILABLE) {
-                values.put(Status.QUOTA_OCCUPIED,occupied);
-            }
-            if (total != QUOTA_UNAVAILABLE) {
-                values.put(Status.QUOTA_TOTAL,total);
-            }
-
-            ContentResolver contentResolver = context.getContentResolver();
-            Uri statusUri = buildSourceUri(context.getPackageName());
-            contentResolver.insert(statusUri, values);
-        }
     }
 }
diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java
index a70c24d..b47e872 100644
--- a/core/java/android/service/carrier/CarrierIdentifier.java
+++ b/core/java/android/service/carrier/CarrierIdentifier.java
@@ -126,4 +126,13 @@
         mGid1 = in.readString();
         mGid2 = in.readString();
     }
+
+    /** @hide */
+    public interface MatchType {
+        int ALL = 0;
+        int SPN = 1;
+        int IMSI_PREFIX = 2;
+        int GID1 = 3;
+        int GID2 = 4;
+    }
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index de8133b..06d87f8 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -55,6 +55,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
 
 import java.io.FileDescriptor;
@@ -628,9 +629,9 @@
                     mCurWindowFlags = mWindowFlags;
                     mLayout.flags = mWindowFlags
                             | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                            | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                             | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            ;
+                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
                     mCurWindowPrivateFlags = mWindowPrivateFlags;
                     mLayout.privateFlags = mWindowPrivateFlags;
 
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 316c7e3..8823605 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -1941,6 +1941,26 @@
     }
 
     /**
+     * Force the transition to move to its end state, ending all the animators.
+     *
+     * @hide
+     */
+    void forceToEnd(ViewGroup sceneRoot) {
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        int numOldAnims = runningAnimators.size();
+        if (sceneRoot != null) {
+            WindowId windowId = sceneRoot.getWindowId();
+            for (int i = numOldAnims - 1; i >= 0; i--) {
+                AnimationInfo info = runningAnimators.valueAt(i);
+                if (info.view != null && windowId != null && windowId.equals(info.windowId)) {
+                    Animator anim = runningAnimators.keyAt(i);
+                    anim.end();
+                }
+            }
+        }
+    }
+
+    /**
      * This method cancels a transition that is currently running.
      *
      * @hide
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 71c8099..f2c871e3 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -440,7 +440,7 @@
             ArrayList<Transition> copy = new ArrayList(runningTransitions);
             for (int i = copy.size() - 1; i >= 0; i--) {
                 final Transition transition = copy.get(i);
-                transition.end();
+                transition.forceToEnd(sceneRoot);
             }
         }
 
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 583dc0f..a41fe64 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -16,8 +16,6 @@
 
 package android.transition;
 
-import com.android.internal.R;
-
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -26,6 +24,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.R;
+
 import java.util.ArrayList;
 
 /**
@@ -498,6 +498,16 @@
         }
     }
 
+    /** @hide */
+    @Override
+    void forceToEnd(ViewGroup sceneRoot) {
+        super.forceToEnd(sceneRoot);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).forceToEnd(sceneRoot);
+        }
+    }
+
     @Override
     TransitionSet setSceneRoot(ViewGroup sceneRoot) {
         super.setSceneRoot(sceneRoot);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index d201ade..f4db4d6 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -66,9 +66,23 @@
      * {@link #DENSITY_XHIGH} (320dpi). This is not a density that applications should target,
      * instead relying on the system to scale their {@link #DENSITY_XHIGH} assets for them.
      */
+    public static final int DENSITY_260 = 260;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_HIGH} (240dpi) and
+     * {@link #DENSITY_XHIGH} (320dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_XHIGH} assets for them.
+     */
     public static final int DENSITY_280 = 280;
 
     /**
+     * Intermediate density for screens that sit between {@link #DENSITY_HIGH} (240dpi) and
+     * {@link #DENSITY_XHIGH} (320dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_XHIGH} assets for them.
+     */
+    public static final int DENSITY_300 = 300;
+
+    /**
      * Standard quantized DPI for extra-high-density screens.
      */
     public static final int DENSITY_XHIGH = 320;
@@ -79,6 +93,14 @@
      * This is not a density that applications should target, instead relying
      * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
      */
+    public static final int DENSITY_340 = 340;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
     public static final int DENSITY_360 = 360;
 
     /**
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 636384c..599c9c72 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -796,8 +796,16 @@
     public static final int KEYCODE_COPY = 278;
     /** Key code constant: Paste key. */
     public static final int KEYCODE_PASTE = 279;
+    /** Key code constant: fingerprint navigation up */
+    public static final int KEYCODE_FP_NAV_UP = 280;
+    /** Key code constant: fingerprint navigation down */
+    public static final int KEYCODE_FP_NAV_DOWN = 281;
+    /** Key code constant: fingerprint navigation left*/
+    public static final int KEYCODE_FP_NAV_LEFT = 282;
+    /** Key code constant: fingerprint navigation right */
+    public static final int KEYCODE_FP_NAV_RIGHT = 283;
 
-    private static final int LAST_KEYCODE = KEYCODE_PASTE;
+    private static final int LAST_KEYCODE = KEYCODE_FP_NAV_RIGHT;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
@@ -1844,6 +1852,10 @@
             case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+            case KeyEvent.KEYCODE_FP_NAV_UP:
+            case KeyEvent.KEYCODE_FP_NAV_DOWN:
+            case KeyEvent.KEYCODE_FP_NAV_LEFT:
+            case KeyEvent.KEYCODE_FP_NAV_RIGHT:
                 return true;
         }
 
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index ab4cbcf..0164fcd 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -793,12 +793,12 @@
         return mOwningView != null && mOwningView.mAttachInfo != null;
     }
 
-    public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimatorRT animatorSet) {
+    public void registerVectorDrawableAnimator(
+            AnimatedVectorDrawable.VectorDrawableAnimatorRT animatorSet) {
         if (mOwningView == null || mOwningView.mAttachInfo == null) {
             throw new IllegalStateException("Cannot start this animator on a detached view!");
         }
-        nAddAnimator(mNativeRenderNode, animatorSet.getAnimatorNativePtr());
-        mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
+        mOwningView.mAttachInfo.mViewRootImpl.registerVectorDrawableAnimator(animatorSet);
     }
 
     public void endAllAnimators() {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index e650d95..fcca739 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -23,6 +23,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -881,6 +882,12 @@
         nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode);
     }
 
+    void registerVectorDrawableAnimator(
+        AnimatedVectorDrawable.VectorDrawableAnimatorRT animator) {
+        nRegisterVectorDrawableAnimator(mRootNode.mNativeRenderNode,
+                animator.getAnimatorNativePtr());
+    }
+
     public void serializeDisplayListTree() {
         nSerializeDisplayListTree(mNativeProxy);
     }
@@ -992,6 +999,7 @@
     private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
     private static native void nDestroy(long nativeProxy, long rootRenderNode);
     private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
+    private static native void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator);
 
     private static native void nInvokeFunctor(long functor, boolean waitForCompletion);
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 19b1cf3..65f0caa 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -42,6 +42,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -824,6 +825,13 @@
         }
     }
 
+    public void registerVectorDrawableAnimator(
+            AnimatedVectorDrawable.VectorDrawableAnimatorRT animator) {
+        if (mAttachInfo.mHardwareRenderer != null) {
+            mAttachInfo.mHardwareRenderer.registerVectorDrawableAnimator(animator);
+        }
+    }
+
     private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
         mAttachInfo.mHardwareAccelerated = false;
         mAttachInfo.mHardwareAccelerationRequested = false;
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 89dec2d..7c3fc8b 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -851,4 +851,9 @@
         
         endBatchEdit();
     }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) { return false; }
 }
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 7b7ccae..7ed0d27 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -25,6 +25,8 @@
 import android.text.TextUtils;
 import android.util.Printer;
 
+import java.util.Arrays;
+
 /**
  * An EditorInfo describes several attributes of a text editing object
  * that an input method is communicating with (typically an EditText), most
@@ -363,6 +365,18 @@
     @Nullable
     public LocaleList hintLocales = null;
 
+
+    /**
+     * List of acceptable MIME types for
+     * {@link InputConnection#insertContent(InputContentInfo, Bundle)}.
+     *
+     * <p>{@code null} or an empty array means that
+     * {@link InputConnection#insertContent(InputContentInfo, Bundle)} is not supported in this
+     * editor.</p>
+     */
+    @Nullable
+    public String[] contentMimeTypes = null;
+
     /**
      * Ensure that the data in this EditorInfo is compatible with an application
      * that was developed against the given target API version.  This can
@@ -418,6 +432,7 @@
                 + " fieldName=" + fieldName);
         pw.println(prefix + "extras=" + extras);
         pw.println(prefix + "hintLocales=" + hintLocales);
+        pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
     }
 
     /**
@@ -446,6 +461,7 @@
         } else {
             LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
         }
+        dest.writeStringArray(contentMimeTypes);
     }
 
     /**
@@ -471,6 +487,7 @@
                     res.extras = source.readBundle();
                     LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
                     res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
+                    res.contentMimeTypes = source.readStringArray();
                     return res;
                 }
 
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 9f66429..05e0569 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -16,6 +16,8 @@
 
 package android.view.inputmethod;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.view.KeyCharacterMap;
@@ -836,4 +838,33 @@
      * <p>Note: This does nothing when called from input methods.</p>
      */
     public void closeConnection();
+
+    /**
+     * Called by the input method to insert a content such as PNG image to the editor.
+     *
+     * <p>In order to avoid variety of compatibility issues, this focuses on a simple use case,
+     * where we expect editors and IMEs work cooperatively as follows:</p>
+     * <ul>
+     *     <li>Editor must keep {@link EditorInfo#contentMimeTypes} to be {@code null} if it does
+     *     not support this method at all.</li>
+     *     <li>Editor can ignore this request when the MIME type specified in
+     *     {@code inputContentInfo} does not match to any of {@link EditorInfo#contentMimeTypes}.
+     *     </li>
+     *     <li>Editor can ignore the cursor position when inserting the provided context.</li>
+     *     <li>Editor can return {@code true} asynchronously, even before it starts loading the
+     *     content.</li>
+     *     <li>Editor should provide a way to delete the content inserted by this method, or revert
+     *     the effect caused by this method.</li>
+     *     <li>IME should not call this method when there is any composing text, in case calling
+     *     this method causes focus change.</li>
+     *     <li>IME should grant a permission for the editor to read the content. See
+     *     {@link EditorInfo#packageName} about how to obtain the package name of the editor.</li>
+     * </ul>
+     *
+     * @param inputContentInfo Content to be inserted.
+     * @param opts optional bundle data. This can be {@code null}.
+     * @return {@code true} if this request is accepted by the application, no matter if the request
+     * is already handled or still being handled in background.
+     */
+    public boolean insertContent(@NonNull InputContentInfo inputContentInfo, @Nullable Bundle opts);
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionInspector.java b/core/java/android/view/inputmethod/InputConnectionInspector.java
index 118a61f..fb24fcd 100644
--- a/core/java/android/view/inputmethod/InputConnectionInspector.java
+++ b/core/java/android/view/inputmethod/InputConnectionInspector.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 
 import java.lang.annotation.Retention;
 import java.lang.reflect.Method;
@@ -41,6 +42,8 @@
             MissingMethodFlags.REQUEST_CURSOR_UPDATES,
             MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
             MissingMethodFlags.GET_HANDLER,
+            MissingMethodFlags.CLOSE_CONNECTION,
+            MissingMethodFlags.INSERT_CONTENT,
     })
     public @interface MissingMethodFlags {
         /**
@@ -78,6 +81,11 @@
          * {@link android.os.Build.VERSION_CODES#N} and later.
          */
         int CLOSE_CONNECTION = 1 << 6;
+        /**
+         * {@link InputConnection#insertContent(InputContentInfo, Bundle)} is available in
+         * {@link android.os.Build.VERSION_CODES#N} MR-1 and later.
+         */
+        int INSERT_CONTENT = 1 << 7;
     }
 
     private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
@@ -127,6 +135,9 @@
         if (!hasCloseConnection(clazz)) {
             flags |= MissingMethodFlags.CLOSE_CONNECTION;
         }
+        if (!hasInsertContent(clazz)) {
+            flags |= MissingMethodFlags.INSERT_CONTENT;
+        }
         sMissingMethodsMap.put(clazz, flags);
         return flags;
     }
@@ -195,6 +206,16 @@
         }
     }
 
+    private static boolean hasInsertContent(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("insertContent", InputContentInfo.class,
+                    Bundle.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
     public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
         final StringBuilder sb = new StringBuilder();
         boolean isEmpty = true;
@@ -242,6 +263,12 @@
             }
             sb.append("closeConnection()");
         }
+        if ((flags & MissingMethodFlags.INSERT_CONTENT) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("InsertContent(InputContentInfo, Bundle)");
+        }
         return sb.toString();
     }
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index e743f62..431836a 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -269,4 +269,12 @@
     public void closeConnection() {
         mTarget.closeConnection();
     }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) {
+        return mTarget.insertContent(inputContentInfo, opts);
+    }
 }
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl b/core/java/android/view/inputmethod/InputContentInfo.aidl
similarity index 73%
copy from telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
copy to core/java/android/view/inputmethod/InputContentInfo.aidl
index b7e78d1..1afeee3b 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
+++ b/core/java/android/view/inputmethod/InputContentInfo.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright 2016, The Android Open Source Project
+ * 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
+ *      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,
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package android.telecom;
+package android.view.inputmethod;
 
-/**
- * {@hide}
- */
-parcelable ParcelableCallAnalytics;
+parcelable InputContentInfo;
diff --git a/core/java/android/view/inputmethod/InputContentInfo.java b/core/java/android/view/inputmethod/InputContentInfo.java
new file mode 100644
index 0000000..e2ecfae
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputContentInfo.java
@@ -0,0 +1,194 @@
+/*
+ * 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 android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+
+/**
+ * A container object with which input methods can send content files to the target application.
+ */
+public class InputContentInfo implements Parcelable {
+
+    @NonNull
+    private final Uri mContentUri;
+    @NonNull
+    private final ClipDescription mDescription;
+    @Nullable
+    private final Uri mLinkUri;
+
+    /**
+     * Constructs {@link InputContentInfo} object only with mandatory data.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     */
+    public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description) {
+        this(contentUri, description, null /* link Uri */);
+    }
+
+    /**
+     * Constructs {@link InputContentInfo} object with additional link URI.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+     * a way to navigate the user to the specified web page if this is not {@code null}.
+     * @throws InvalidParameterException if any invalid parameter is specified.
+     */
+    public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description,
+            @Nullable Uri linkUri) {
+        validateInternal(contentUri, description, linkUri, true /* throwException */);
+        mContentUri = contentUri;
+        mDescription = description;
+        mLinkUri = linkUri;
+    }
+
+    /**
+     * @return {@code true} if all the fields are valid.
+     * @hide
+     */
+    public boolean validate() {
+        return validateInternal(mContentUri, mDescription, mLinkUri, false /* throwException */);
+    }
+
+    /**
+     * Constructs {@link InputContentInfo} object with additional link URI.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+     * a way to navigate the user to the specified web page if this is not {@code null}.
+     * @param throwException {@code true} if this method should throw an
+     * {@link InvalidParameterException}.
+     * @throws InvalidParameterException if any invalid parameter is specified.
+     */
+    private static boolean validateInternal(@NonNull Uri contentUri,
+            @NonNull ClipDescription description, @Nullable Uri linkUri, boolean throwException) {
+        if (contentUri == null) {
+            if (throwException) {
+                throw new NullPointerException("contentUri");
+            }
+            return false;
+        }
+        if (description == null) {
+            if (throwException) {
+                throw new NullPointerException("description");
+            }
+            return false;
+        }
+        final String contentUriScheme = contentUri.getScheme();
+        if (contentUriScheme == null || !contentUriScheme.equalsIgnoreCase("content")) {
+            if (throwException) {
+                throw new InvalidParameterException("contentUri must have content scheme");
+            }
+            return false;
+        }
+        if (linkUri != null) {
+            final String scheme = linkUri.getScheme();
+            if (scheme == null ||
+                    (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
+                if (throwException) {
+                    throw new InvalidParameterException(
+                            "linkUri must have either http or https scheme");
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return Content URI with which the content can be obtained.
+     */
+    @NonNull
+    public Uri getContentUri() { return mContentUri; }
+
+    /**
+     * @return {@link ClipDescription} object that contains the metadata of {@code contentUri} such
+     * as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility purpose.
+     */
+    @NonNull
+    public ClipDescription getDescription() { return mDescription; }
+
+    /**
+     * @return An optional {@code http} or {@code https} URI that is related to this content.
+     */
+    @Nullable
+    public Uri getLinkUri() { return mLinkUri; }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        Uri.writeToParcel(dest, mContentUri);
+        mDescription.writeToParcel(dest, flags);
+        Uri.writeToParcel(dest, mLinkUri);
+    }
+
+    private InputContentInfo(@NonNull Parcel source) {
+        mContentUri = Uri.CREATOR.createFromParcel(source);
+        mDescription = ClipDescription.CREATOR.createFromParcel(source);
+        mLinkUri = Uri.CREATOR.createFromParcel(source);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<InputContentInfo> CREATOR
+            = new Parcelable.Creator<InputContentInfo>() {
+        @Override
+        public InputContentInfo createFromParcel(Parcel source) {
+            return new InputContentInfo(source);
+        }
+
+        @Override
+        public InputContentInfo[] newArray(int size) {
+            return new InputContentInfo[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index b331be7..fb532e2 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,6 +19,7 @@
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
@@ -75,6 +76,7 @@
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.RemoteViews.OnClickHandler;
 
@@ -5982,6 +5984,11 @@
         public void closeConnection() {
             getTarget().closeConnection();
         }
+
+        @Override
+        public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) {
+            return getTarget().insertContent(inputContentInfo, opts);
+        }
     }
 
     /**
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1f745185..c33288b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3195,7 +3195,9 @@
         // we don't add a filter to the static version returned by getSystemService.
         inflater = inflater.cloneInContext(inflationContext);
         inflater.setFilter(this);
-        return inflater.inflate(rv.getLayoutId(), parent, false);
+        View v = inflater.inflate(rv.getLayoutId(), parent, false);
+        v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
+        return v;
     }
 
     private static void loadTransitionOverride(Context context,
@@ -3373,7 +3375,7 @@
         // across orientation change, and has the RemoteViews re-applied in the new orientation,
         // we throw an exception, since the layouts may be completely unrelated.
         if (hasLandscapeAndPortraitLayouts()) {
-            if (v.getId() != rvToApply.getLayoutId()) {
+            if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
             }
@@ -3409,7 +3411,7 @@
         // across orientation change, and has the RemoteViews re-applied in the new orientation,
         // we throw an exception, since the layouts may be completely unrelated.
         if (hasLandscapeAndPortraitLayouts()) {
-            if (v.getId() != rvToApply.getLayoutId()) {
+            if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
             }
diff --git a/core/java/com/android/internal/app/AlertActivity.java b/core/java/com/android/internal/app/AlertActivity.java
index ed48b0d..35ffa71 100644
--- a/core/java/com/android/internal/app/AlertActivity.java
+++ b/core/java/com/android/internal/app/AlertActivity.java
@@ -49,7 +49,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mAlert = new AlertController(this, this, getWindow());
+        mAlert = AlertController.create(this, this, getWindow());
         mAlertParams = new AlertController.AlertParams(this);
     }
 
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index b7ac600..2a40aeb 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -24,6 +24,7 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
@@ -61,14 +62,15 @@
 import java.lang.ref.WeakReference;
 
 public class AlertController {
+    public static final int MICRO = 1;
 
     private final Context mContext;
     private final DialogInterface mDialogInterface;
-    private final Window mWindow;
+    protected final Window mWindow;
 
     private CharSequence mTitle;
-    private CharSequence mMessage;
-    private ListView mListView;
+    protected CharSequence mMessage;
+    protected ListView mListView;
     private View mView;
 
     private int mViewLayoutResId;
@@ -91,14 +93,14 @@
     private CharSequence mButtonNeutralText;
     private Message mButtonNeutralMessage;
 
-    private ScrollView mScrollView;
+    protected ScrollView mScrollView;
 
     private int mIconId = 0;
     private Drawable mIcon;
 
     private ImageView mIconView;
     private TextView mTitleView;
-    private TextView mMessageView;
+    protected TextView mMessageView;
     private View mCustomTitleView;
 
     private boolean mForceInverseBackground;
@@ -176,7 +178,26 @@
         return outValue.data != 0;
     }
 
-    public AlertController(Context context, DialogInterface di, Window window) {
+    private static boolean isWatch(Context context) {
+        return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_WATCH)
+                == Configuration.UI_MODE_TYPE_WATCH;
+    }
+
+    public static final AlertController create(Context context, DialogInterface di, Window window) {
+        final TypedArray a = context.obtainStyledAttributes(
+                null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+        int controllerType = a.getInt(R.styleable.AlertDialog_controllerType, 0);
+        a.recycle();
+
+        switch (controllerType) {
+            case MICRO:
+                return new MicroAlertController(context, di, window);
+            default:
+                return new AlertController(context, di, window);
+        }
+    }
+
+    protected AlertController(Context context, DialogInterface di, Window window) {
         mContext = context;
         mDialogInterface = di;
         mWindow = window;
@@ -643,7 +664,7 @@
         }
     }
 
-    private void setupContent(ViewGroup contentPanel) {
+    protected void setupContent(ViewGroup contentPanel) {
         mScrollView = (ScrollView) contentPanel.findViewById(R.id.scrollView);
         mScrollView.setFocusable(false);
 
@@ -871,8 +892,14 @@
             listView.setAdapter(mAdapter);
             final int checkedItem = mCheckedItem;
             if (checkedItem > -1) {
-                listView.setItemChecked(checkedItem, true);
-                listView.setSelection(checkedItem);
+                // TODO: Remove temp watch specific code
+                if (isWatch(mContext)) {
+                    listView.setItemChecked(checkedItem + listView.getHeaderViewsCount(), true);
+                    listView.setSelection(checkedItem + listView.getHeaderViewsCount());
+                } else {
+                    listView.setItemChecked(checkedItem, true);
+                    listView.setSelection(checkedItem);
+                }
             }
         }
     }
@@ -1051,7 +1078,13 @@
                             if (mCheckedItems != null) {
                                 boolean isItemChecked = mCheckedItems[position];
                                 if (isItemChecked) {
-                                    listView.setItemChecked(position, true);
+                                    // TODO: Remove temp watch specific code
+                                    if (isWatch(mContext)) {
+                                        listView.setItemChecked(
+                                                position + listView.getHeaderViewsCount(), true);
+                                    } else {
+                                        listView.setItemChecked(position, true);
+                                    }
                                 }
                             }
                             return view;
@@ -1072,8 +1105,16 @@
                         public void bindView(View view, Context context, Cursor cursor) {
                             CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
                             text.setText(cursor.getString(mLabelIndex));
-                            listView.setItemChecked(cursor.getPosition(),
-                                    cursor.getInt(mIsCheckedIndex) == 1);
+                            // TODO: Remove temp watch specific code
+                            if (isWatch(mContext)) {
+                                listView.setItemChecked(
+                                        cursor.getPosition() + listView.getHeaderViewsCount(),
+                                        cursor.getInt(mIsCheckedIndex) == 1);
+                            } else {
+                                listView.setItemChecked(
+                                        cursor.getPosition(),
+                                        cursor.getInt(mIsCheckedIndex) == 1);
+                            }
                         }
 
                         @Override
@@ -1116,6 +1157,10 @@
                 listView.setOnItemClickListener(new OnItemClickListener() {
                     @Override
                     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+                        // TODO: Remove temp watch specific code
+                        if (isWatch(mContext)) {
+                            position -= listView.getHeaderViewsCount();
+                        }
                         mOnClickListener.onClick(dialog.mDialogInterface, position);
                         if (!mIsSingleChoice) {
                             dialog.mDialogInterface.dismiss();
@@ -1126,6 +1171,10 @@
                 listView.setOnItemClickListener(new OnItemClickListener() {
                     @Override
                     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+                        // TODO: Remove temp watch specific code
+                        if (isWatch(mContext)) {
+                            position -= listView.getHeaderViewsCount();
+                        }
                         if (mCheckedItems != null) {
                             mCheckedItems[position] = listView.isItemChecked(position);
                         }
diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java
index 6ba04a9..68724a7 100644
--- a/core/java/com/android/internal/app/EphemeralResolverService.java
+++ b/core/java/com/android/internal/app/EphemeralResolverService.java
@@ -39,14 +39,19 @@
 public abstract class EphemeralResolverService extends Service {
     public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO";
     public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE";
+    private static final String EXTRA_PREFIX = "com.android.internal.app.PREFIX";
     private Handler mHandler;
 
     /**
      * Called to retrieve resolve info for ephemeral applications.
      *
      * @param digestPrefix The hash prefix of the ephemeral's domain.
+     * @param prefixMask A mask that was applied to each digest prefix. This should
+     *      be used when comparing against the digest prefixes as all bits might
+     *      not be set.
      */
-    protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix);
+    protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(
+            int digestPrefix[], int prefixMask);
 
     @Override
     protected final void attachBaseContext(Context base) {
@@ -59,10 +64,13 @@
         return new IEphemeralResolver.Stub() {
             @Override
             public void getEphemeralResolveInfoList(
-                    IRemoteCallback callback, int digestPrefix, int sequence) {
-                mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO,
-                        digestPrefix, sequence, callback)
-                    .sendToTarget();
+                    IRemoteCallback callback, int digestPrefix[], int prefixMask, int sequence) {
+                final Message msg = mHandler.obtainMessage(
+                        ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO, prefixMask, sequence, callback);
+                final Bundle data = new Bundle();
+                data.putIntArray(EXTRA_PREFIX, digestPrefix);
+                msg.setData(data);
+                msg.sendToTarget();
             }
         };
     }
@@ -81,8 +89,9 @@
             switch (action) {
                 case MSG_GET_EPHEMERAL_RESOLVE_INFO: {
                     final IRemoteCallback callback = (IRemoteCallback) message.obj;
+                    final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
                     final List<EphemeralResolveInfo> resolveInfo =
-                            getEphemeralResolveInfoList(message.arg1);
+                            getEphemeralResolveInfoList(digestPrefix, message.arg1);
                     final Bundle data = new Bundle();
                     data.putInt(EXTRA_SEQUENCE, message.arg2);
                     data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
diff --git a/core/java/com/android/internal/app/IEphemeralResolver.aidl b/core/java/com/android/internal/app/IEphemeralResolver.aidl
index 40429ee..9ff1322 100644
--- a/core/java/com/android/internal/app/IEphemeralResolver.aidl
+++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl
@@ -20,5 +20,6 @@
 import android.os.IRemoteCallback;
 
 oneway interface IEphemeralResolver {
-    void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence);
+    void getEphemeralResolveInfoList(IRemoteCallback callback, in int[] digestPrefix,
+            int prefixMask, int sequence);
 }
diff --git a/core/java/com/android/internal/app/MicroAlertController.java b/core/java/com/android/internal/app/MicroAlertController.java
new file mode 100644
index 0000000..085b226
--- /dev/null
+++ b/core/java/com/android/internal/app/MicroAlertController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.AbsListView;
+
+import com.android.internal.app.AlertController;
+import com.android.internal.R;
+
+public class MicroAlertController extends AlertController {
+    public MicroAlertController(Context context, DialogInterface di, Window window) {
+        super(context, di, window);
+    }
+
+    @Override
+    protected void setupContent(ViewGroup contentPanel) {
+        // Special case for small screen - the scroll view is higher in hierarchy
+        mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
+
+        // Special case for users that only want to display a String
+        mMessageView = (TextView) contentPanel.findViewById(R.id.message);
+        if (mMessageView == null) {
+            return;
+        }
+
+        if (mMessage != null) {
+            mMessageView.setText(mMessage);
+        } else {
+            // no message, remove associated views
+            mMessageView.setVisibility(View.GONE);
+            contentPanel.removeView(mMessageView);
+
+            if (mListView != null) {
+                // has ListView, swap ScrollView with ListView
+
+                // move topPanel into header of ListView
+                View topPanel = mScrollView.findViewById(R.id.topPanel);
+                ((ViewGroup) topPanel.getParent()).removeView(topPanel);
+                topPanel.setLayoutParams(
+                        new AbsListView.LayoutParams(topPanel.getLayoutParams()));
+                mListView.addHeaderView(topPanel, null, false);
+
+                // move buttonPanel into footer of ListView
+                View buttonPanel = mScrollView.findViewById(R.id.buttonPanel);
+                ((ViewGroup) buttonPanel.getParent()).removeView(buttonPanel);
+                buttonPanel.setLayoutParams(
+                        new AbsListView.LayoutParams(buttonPanel.getLayoutParams()));
+                mListView.addFooterView(buttonPanel, null, false);
+
+                // swap ScrollView w/ ListView
+                final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent();
+                final int childIndex = scrollParent.indexOfChild(mScrollView);
+                scrollParent.removeViewAt(childIndex);
+                scrollParent.addView(mListView, childIndex,
+                        new ViewGroup.LayoutParams(
+                                ViewGroup.LayoutParams.MATCH_PARENT,
+                                ViewGroup.LayoutParams.MATCH_PARENT));
+            } else {
+                // no content, just hide everything
+                contentPanel.setVisibility(View.GONE);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 085e159..0a4ac0d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -69,9 +69,13 @@
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
 import com.android.internal.widget.ResolverDrawerLayout;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -359,6 +363,12 @@
         if (isVoiceInteraction()) {
             onSetupVoiceInteraction();
         }
+        final Set<String> categories = intent.getCategories();
+        MetricsLogger.action(this, mAdapter.hasFilteredItem()
+                ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
+                : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
+                intent.getAction() + ":" + intent.getType() + ":"
+                        + (categories != null ? Arrays.toString(categories.toArray()) : ""));
     }
 
     public final void setFilteredComponents(ComponentName[] components) {
@@ -649,6 +659,19 @@
 
         TargetInfo target = mAdapter.targetInfoForPosition(which, filtered);
         if (onTargetSelected(target, always)) {
+            if (always && filtered) {
+                MetricsLogger.action(
+                        this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
+            } else if (filtered) {
+                MetricsLogger.action(
+                        this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
+            } else {
+                MetricsLogger.action(
+                        this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
+            }
+            MetricsLogger.action(this, mAdapter.hasFilteredItem()
+                            ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
+                            : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
             finish();
         }
     }
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 4260e50..951a45a 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -34,7 +34,7 @@
     // for AppWidgetHost
     //
     ParceledListSlice startListening(IAppWidgetHost host, String callingPackage, int hostId,
-            in int[] appWidgetIds, out int[] updatedIds);
+            in int[] appWidgetIds);
     void stopListening(String callingPackage, int hostId);
     int allocateAppWidgetId(String callingPackage, int hostId);
     void deleteAppWidgetId(String callingPackage, int appWidgetId);
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
index 28281b3c..8addffb0 100644
--- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -370,6 +370,8 @@
                 systemInsets.bottom);
         final int rightInset = DecorView.getColorViewRightInset(stableInsets.right,
                 systemInsets.right);
+        final int leftInset = DecorView.getColorViewLeftInset(stableInsets.left,
+                systemInsets.left);
         if (mStatusBarColor != null) {
             mStatusBarColor.setBounds(0, 0, left + width, topInset);
             mStatusBarColor.draw(canvas);
@@ -379,9 +381,11 @@
         // don't want the navigation bar background be moving around when resizing in docked mode.
         // However, we need it for the transitions into/out of docked mode.
         if (mNavigationBarColor != null && fullscreen) {
-            final int size = DecorView.getNavBarSize(bottomInset, rightInset);
+            final int size = DecorView.getNavBarSize(bottomInset, rightInset, leftInset);
             if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) {
                 mNavigationBarColor.setBounds(width - size, 0, width, height);
+            } else if (DecorView.isNavBarToLeftEdge(bottomInset, rightInset)) {
+                mNavigationBarColor.setBounds(0, 0, size, height);
             } else {
                 mNavigationBarColor.setBounds(0, height - size, width, height);
             }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 3cf7a4e..366fc1a 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -97,7 +97,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -162,13 +161,13 @@
 
     private final ColorViewState mStatusColorViewState = new ColorViewState(
             SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
-            Gravity.TOP, Gravity.LEFT,
+            Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
             Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
             com.android.internal.R.id.statusBarBackground,
             FLAG_FULLSCREEN);
     private final ColorViewState mNavigationColorViewState = new ColorViewState(
             SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
-            Gravity.BOTTOM, Gravity.RIGHT,
+            Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
             Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
             com.android.internal.R.id.navigationBarBackground,
             0 /* hideWindowFlag */);
@@ -184,9 +183,11 @@
     private int mLastTopInset = 0;
     private int mLastBottomInset = 0;
     private int mLastRightInset = 0;
+    private int mLastLeftInset = 0;
     private boolean mLastHasTopStableInset = false;
     private boolean mLastHasBottomStableInset = false;
     private boolean mLastHasRightStableInset = false;
+    private boolean mLastHasLeftStableInset = false;
     private int mLastWindowFlags = 0;
     private boolean mLastShouldAlwaysConsumeNavBar = false;
 
@@ -991,12 +992,21 @@
         return Math.min(stableRight, systemRight);
     }
 
+    static int getColorViewLeftInset(int stableLeft, int systemLeft) {
+        return Math.min(stableLeft, systemLeft);
+    }
+
     static boolean isNavBarToRightEdge(int bottomInset, int rightInset) {
         return bottomInset == 0 && rightInset > 0;
     }
 
-    static int getNavBarSize(int bottomInset, int rightInset) {
-        return isNavBarToRightEdge(bottomInset, rightInset) ? rightInset : bottomInset;
+    static boolean isNavBarToLeftEdge(int bottomInset, int leftInset) {
+        return bottomInset == 0 && leftInset > 0;
+    }
+
+    static int getNavBarSize(int bottomInset, int rightInset, int leftInset) {
+        return isNavBarToRightEdge(bottomInset, rightInset) ? rightInset
+                : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset;
     }
 
     WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
@@ -1016,6 +1026,8 @@
                         insets.getSystemWindowInsetBottom());
                 mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(),
                         insets.getSystemWindowInsetRight());
+                mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(),
+                        insets.getSystemWindowInsetLeft());
 
                 // Don't animate if the presence of stable insets has changed, because that
                 // indicates that the window was either just added and received them for the
@@ -1031,21 +1043,32 @@
                 boolean hasRightStableInset = insets.getStableInsetRight() != 0;
                 disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
                 mLastHasRightStableInset = hasRightStableInset;
+
+                boolean hasLeftStableInset = insets.getStableInsetLeft() != 0;
+                disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset);
+                mLastHasLeftStableInset = hasLeftStableInset;
+
                 mLastShouldAlwaysConsumeNavBar = insets.shouldAlwaysConsumeNavBar();
             }
 
             boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
-            int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset);
+            boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
+            int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
             updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
-                    mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
-                    0 /* rightInset */, animate && !disallowAnimate, false /* force */);
+                    mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge || navBarToLeftEdge,
+                    navBarToLeftEdge,
+                    0 /* sideInset */, animate && !disallowAnimate, false /* force */);
 
             boolean statusBarNeedsRightInset = navBarToRightEdge
                     && mNavigationColorViewState.present;
-            int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
+            boolean statusBarNeedsLeftInset = navBarToLeftEdge
+                    && mNavigationColorViewState.present;
+            int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
+                    : statusBarNeedsLeftInset ? mLastLeftInset : 0;
             updateColorViewInt(mStatusColorViewState, sysUiVisibility,
                     calculateStatusBarColor(), mLastTopInset,
-                    false /* matchVertical */, statusBarRightInset, animate && !disallowAnimate,
+                    false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
+                    animate && !disallowAnimate,
                     mForceWindowDrawsStatusBarBackground);
         }
 
@@ -1070,15 +1093,17 @@
         int consumedTop = consumingStatusBar ? mLastTopInset : 0;
         int consumedRight = consumingNavBar ? mLastRightInset : 0;
         int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+        int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
 
         if (mContentRoot != null
                 && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
             MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
             if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
-                    || lp.bottomMargin != consumedBottom) {
+                    || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {
                 lp.topMargin = consumedTop;
                 lp.rightMargin = consumedRight;
                 lp.bottomMargin = consumedBottom;
+                lp.leftMargin = consumedLeft;
                 mContentRoot.setLayoutParams(lp);
 
                 if (insets == null) {
@@ -1089,7 +1114,7 @@
             }
             if (insets != null) {
                 insets = insets.replaceSystemWindowInsets(
-                        insets.getSystemWindowInsetLeft(),
+                        insets.getSystemWindowInsetLeft() - consumedLeft,
                         insets.getSystemWindowInsetTop() - consumedTop,
                         insets.getSystemWindowInsetRight() - consumedRight,
                         insets.getSystemWindowInsetBottom() - consumedBottom);
@@ -1126,11 +1151,12 @@
      * @param size the current size in the non-parent-matching dimension.
      * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
      *                    horizontal edge,
-     * @param rightMargin rightMargin for the color view.
+     * @param sideMargin sideMargin for the color view.
      * @param animate if true, the change will be animated.
      */
     private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
-            int size, boolean verticalBar, int rightMargin, boolean animate, boolean force) {
+            int size, boolean verticalBar, boolean seascape, int sideMargin,
+            boolean animate, boolean force) {
         state.present = (sysUiVis & state.systemUiHideFlag) == 0
                 && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
                 && ((mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
@@ -1145,7 +1171,9 @@
 
         int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
         int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
-        int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
+        int resolvedGravity = verticalBar
+                ? (seascape ? state.seascapeGravity : state.horizontalGravity)
+                : state.verticalGravity;
 
         if (view == null) {
             if (showView) {
@@ -1159,7 +1187,11 @@
 
                 LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
                         resolvedGravity);
-                lp.rightMargin = rightMargin;
+                if (seascape) {
+                    lp.leftMargin = sideMargin;
+                } else {
+                    lp.rightMargin = sideMargin;
+                }
                 addView(view, lp);
                 updateColorViewTranslations();
             }
@@ -1168,12 +1200,16 @@
             visibilityChanged = state.targetVisibility != vis;
             state.targetVisibility = vis;
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            int rightMargin = seascape ? 0 : sideMargin;
+            int leftMargin = seascape ? sideMargin : 0;
             if (lp.height != resolvedHeight || lp.width != resolvedWidth
-                    || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
+                    || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin
+                    || lp.leftMargin != leftMargin) {
                 lp.height = resolvedHeight;
                 lp.width = resolvedWidth;
                 lp.gravity = resolvedGravity;
                 lp.rightMargin = rightMargin;
+                lp.leftMargin = leftMargin;
                 view.setLayoutParams(lp);
             }
             if (showView) {
@@ -2217,17 +2253,19 @@
         final int translucentFlag;
         final int verticalGravity;
         final int horizontalGravity;
+        final int seascapeGravity;
         final String transitionName;
         final int hideWindowFlag;
 
         ColorViewState(int systemUiHideFlag,
                 int translucentFlag, int verticalGravity, int horizontalGravity,
-                String transitionName, int id, int hideWindowFlag) {
+                int seascapeGravity, String transitionName, int id, int hideWindowFlag) {
             this.id = id;
             this.systemUiHideFlag = systemUiHideFlag;
             this.translucentFlag = translucentFlag;
             this.verticalGravity = verticalGravity;
             this.horizontalGravity = horizontalGravity;
+            this.seascapeGravity = seascapeGravity;
             this.transitionName = transitionName;
             this.hideWindowFlag = hideWindowFlag;
         }
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
index 2653745..7d222c7 100644
--- a/core/java/com/android/internal/util/WakeupMessage.java
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -45,24 +45,32 @@
     protected final String mCmdName;
     @VisibleForTesting
     protected final int mCmd, mArg1, mArg2;
+    @VisibleForTesting
+    protected final Object mObj;
     private boolean mScheduled;
 
     public WakeupMessage(Context context, Handler handler,
-            String cmdName, int cmd, int arg1, int arg2) {
+            String cmdName, int cmd, int arg1, int arg2, Object obj) {
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mHandler = handler;
         mCmdName = cmdName;
         mCmd = cmd;
         mArg1 = arg1;
         mArg2 = arg2;
+        mObj = obj;
     }
 
     public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
-        this(context, handler, cmdName, cmd, arg1, 0);
+        this(context, handler, cmdName, cmd, arg1, 0, null);
+    }
+
+    public WakeupMessage(Context context, Handler handler,
+            String cmdName, int cmd, int arg1, int arg2) {
+        this(context, handler, cmdName, cmd, arg1, arg2, null);
     }
 
     public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
-        this(context, handler, cmdName, cmd, 0, 0);
+        this(context, handler, cmdName, cmd, 0, 0, null);
     }
 
     /**
@@ -99,7 +107,7 @@
             mScheduled = false;
         }
         if (stillScheduled) {
-            Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2);
+            Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
             mHandler.handleMessage(msg);
             msg.recycle();
         }
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 59b4b35..fc189a3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -33,6 +33,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionInspector;
 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
+import android.view.inputmethod.InputContentInfo;
 
 public abstract class IInputConnectionWrapper extends IInputContext.Stub {
     static final String TAG = "IInputConnectionWrapper";
@@ -61,6 +62,7 @@
     private static final int DO_CLEAR_META_KEY_STATES = 130;
     private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
     private static final int DO_CLOSE_CONNECTION = 150;
+    private static final int DO_INSERT_CONTENT = 160;
 
     @GuardedBy("mLock")
     @Nullable
@@ -241,6 +243,11 @@
         dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION));
     }
 
+    public void insertContent(InputContentInfo inputContentInfo, Bundle opts,
+            int seq, IInputContextCallback callback) {
+        dispatchMessage(obtainMessageOOSC(DO_INSERT_CONTENT, inputContentInfo, opts, seq, callback));
+    }
+
     void dispatchMessage(Message msg) {
         // If we are calling this from the main thread, then we can call
         // right through.  Otherwise, we need to send the message to the
@@ -552,6 +559,29 @@
                 }
                 return;
             }
+            case DO_INSERT_CONTENT: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                try {
+                    InputConnection ic = getInputConnection();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "insertContent on inactive InputConnection");
+                        args.callback.setInsertContentResult(false, args.seq);
+                        return;
+                    }
+                    final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
+                    if (inputContentInfo == null || !inputContentInfo.validate()) {
+                        Log.w(TAG, "insertContent with invalid inputContentInfo="
+                                + inputContentInfo);
+                        args.callback.setInsertContentResult(false, args.seq);
+                        return;
+                    }
+                    args.callback.setInsertContentResult(
+                            ic.insertContent(inputContentInfo, (Bundle) args.arg2), args.seq);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Got RemoteException calling insertContent", e);
+                }
+                return;
+            }
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -582,9 +612,11 @@
         return mH.obtainMessage(what, arg1, arg2, args);
     }
 
-    Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) {
+    Message obtainMessageOOSC(int what, Object arg1, Object arg2, int seq,
+            IInputContextCallback callback) {
         SomeArgs args = new SomeArgs();
         args.arg1 = arg1;
+        args.arg2 = arg2;
         args.callback = callback;
         args.seq = seq;
         return mH.obtainMessage(what, 0, 0, args);
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index f7ec420..bd47355 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -21,6 +21,7 @@
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputContentInfo;
 
 import com.android.internal.view.IInputContextCallback;
 
@@ -74,6 +75,9 @@
 
     void getSelectedText(int flags, int seq, IInputContextCallback callback);
 
-    void requestUpdateCursorAnchorInfo(in int cursorUpdateMode, int seq,
+    void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
+            IInputContextCallback callback);
+
+    void insertContent(in InputContentInfo inputContentInfo, in Bundle opts, int sec,
             IInputContextCallback callback);
 }
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
index 54ea306..933cdc0 100644
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ b/core/java/com/android/internal/view/IInputContextCallback.aidl
@@ -28,4 +28,5 @@
     void setExtractedText(in ExtractedText extractedText, int seq);
     void setSelectedText(CharSequence selectedText, int seq);
     void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq);
+    void setInsertContentResult(boolean result, int seq);
 }
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index f9884d8..6d4d45c 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.view;
 
+import android.content.ClipData;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -29,6 +30,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionInspector;
 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
+import android.view.inputmethod.InputContentInfo;
 
 public class InputConnectionWrapper implements InputConnection {
     private static final int MAX_WAIT_TIME_MILLIS = 2000;
@@ -46,7 +48,8 @@
         public ExtractedText mExtractedText;
         public int mCursorCapsMode;
         public boolean mRequestUpdateCursorAnchorInfoResult;
-        
+        public boolean mInsertContentResult;
+
         // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
         // exclusive access to this object.
         private static InputContextCallback sInstance = new InputContextCallback();
@@ -172,6 +175,19 @@
             }
         }
 
+        public void setInsertContentResult(boolean result, int seq) {
+            synchronized (this) {
+                if (seq == mSeq) {
+                    mInsertContentResult = result;
+                    mHaveValue = true;
+                    notifyAll();
+                } else {
+                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+                            + ") in setInsertContentResult, ignoring.");
+                }
+            }
+        }
+
         /**
          * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
          * 
@@ -491,6 +507,28 @@
         // Nothing should happen when called from input method.
     }
 
+    public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) {
+        boolean result = false;
+        if (isMethodMissing(MissingMethodFlags.INSERT_CONTENT)) {
+            // This method is not implemented.
+            return false;
+        }
+        try {
+            InputContextCallback callback = InputContextCallback.getInstance();
+            mIInputContext.insertContent(inputContentInfo, opts, callback.mSeq, callback);
+            synchronized (callback) {
+                callback.waitForResultLocked();
+                if (callback.mHaveValue) {
+                    result = callback.mInsertContentResult;
+                }
+            }
+            callback.dispose();
+        } catch (RemoteException e) {
+            return false;
+        }
+        return result;
+    }
+
     private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
         return (mMissingMethods & methodFlag) == methodFlag;
     }
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
index 9e2cbdb..d28ab07 100644
--- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
+++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
@@ -30,6 +30,8 @@
 @HasNativeInterpolator
 public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeInterpolator {
 
+    // If the duration of an animation is more than 300 frames, we cap the sample size to 300.
+    private static final int MAX_SAMPLE_POINTS = 300;
     private TimeInterpolator mSourceInterpolator;
     private final float mLut[];
 
@@ -47,6 +49,7 @@
         int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
         // We need 2 frame values as the minimal.
         int numAnimFrames = Math.max(2, (int) Math.ceil(((double) duration) / animIntervalMs));
+        numAnimFrames = Math.min(numAnimFrames, MAX_SAMPLE_POINTS);
         float values[] = new float[numAnimFrames];
         float lastFrame = numAnimFrames - 1;
         for (int i = 0; i < numAnimFrames; i++) {
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
index 4b2a72d..47252ad 100644
--- a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -95,6 +95,12 @@
     return reinterpret_cast<jlong>(animatorSet);
 }
 
+static void setVectorDrawableTarget(JNIEnv*, jobject,jlong animatorPtr, jlong vectorDrawablePtr) {
+    VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(vectorDrawablePtr);
+    PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorPtr);
+    set->setVectorDrawable(tree);
+}
+
 static jlong createGroupPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId,
         jfloat startValue, jfloat endValue) {
     VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(nativePtr);
@@ -136,14 +142,24 @@
             startValue, endValue);
     return reinterpret_cast<jlong>(newHolder);
 }
-static void setPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr,
+static void setFloatPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr,
         jfloatArray srcData, jint length) {
-
     jfloat* propertyData = env->GetFloatArrayElements(srcData, nullptr);
-    PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr);
+    PropertyValuesHolderImpl<float>* holder =
+            reinterpret_cast<PropertyValuesHolderImpl<float>*>(propertyHolderPtr);
     holder->setPropertyDataSource(propertyData, length);
     env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
 }
+
+static void setIntPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr,
+        jintArray srcData, jint length) {
+    jint* propertyData = env->GetIntArrayElements(srcData, nullptr);
+    PropertyValuesHolderImpl<int>* holder =
+            reinterpret_cast<PropertyValuesHolderImpl<int>*>(propertyHolderPtr);
+    holder->setPropertyDataSource(propertyData, length);
+    env->ReleaseIntArrayElements(srcData, propertyData, JNI_ABORT);
+}
+
 static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
     PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
     AnimationListener* listener = createAnimationListener(env, finishListener, id);
@@ -168,13 +184,15 @@
 
 static const JNINativeMethod gMethods[] = {
     {"nCreateAnimatorSet", "()J", (void*)createAnimatorSet},
+    {"nSetVectorDrawableTarget", "(JJ)V", (void*)setVectorDrawableTarget},
     {"nAddAnimator", "(JJJJJI)V", (void*)addAnimator},
     {"nCreateGroupPropertyHolder", "!(JIFF)J", (void*)createGroupPropertyHolder},
     {"nCreatePathDataPropertyHolder", "!(JJJ)J", (void*)createPathDataPropertyHolder},
     {"nCreatePathColorPropertyHolder", "!(JIII)J", (void*)createPathColorPropertyHolder},
     {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
     {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
-    {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
+    {"nSetPropertyHolderData", "(J[FI)V", (void*)setFloatPropertyHolderData},
+    {"nSetPropertyHolderData", "(J[II)V", (void*)setIntPropertyHolderData},
     {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT;I)V", (void*)start},
     {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT;I)V", (void*)reverse},
     {"nEnd", "!(J)V", (void*)end},
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 172e12d..2dcf8aa 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -32,6 +32,7 @@
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
 #include <utils/StrongPointer.h>
+#include <utils/Timers.h>
 #include <android_runtime/android_view_Surface.h>
 #include <system/window.h>
 
@@ -44,6 +45,7 @@
 #include <FrameMetricsObserver.h>
 #include <IContextFactory.h>
 #include <JankTracker.h>
+#include <PropertyValuesAnimatorSet.h>
 #include <RenderNode.h>
 #include <renderthread/CanvasContext.h>
 #include <renderthread/RenderProxy.h>
@@ -122,6 +124,31 @@
     std::vector<OnFinishedEvent> mOnFinishedEvents;
 };
 
+class FinishAndInvokeListener : public MessageHandler {
+public:
+    explicit FinishAndInvokeListener(PropertyValuesAnimatorSet* anim)
+            : mAnimator(anim) {
+        mListener = anim->getOneShotListener();
+        mRequestId = anim->getRequestId();
+    }
+
+    virtual void handleMessage(const Message& message) {
+        if (mAnimator->getRequestId() == mRequestId) {
+            // Request Id has not changed, meaning there's no animation lifecyle change since the
+            // message is posted, so go ahead and call finish to make sure the PlayState is properly
+            // updated. This is needed because before the next frame comes in from UI thread to
+            // trigger an animation update, there could be reverse/cancel etc. So we need to update
+            // the playstate in time to ensure all the subsequent events get chained properly.
+            mAnimator->end();
+        }
+        mListener->onAnimationFinished(nullptr);
+    }
+private:
+    sp<PropertyValuesAnimatorSet> mAnimator;
+    sp<AnimationListener> mListener;
+    uint32_t mRequestId;
+};
+
 class RenderingException : public MessageHandler {
 public:
     RenderingException(JavaVM* vm, const std::string& message)
@@ -160,6 +187,15 @@
 
     virtual void prepareTree(TreeInfo& info) override {
         info.errorHandler = this;
+
+        for (auto& anim : mVectorDrawableAnimators) {
+            // Assume that the property change in VD from the animators will not be consumed. Mark
+            // otherwise if the VDs are found in the display list tree. For VDs that are not in
+            // the display list tree, we stop providing animation pulses by 1) removing them from
+            // the animation list, 2) post a delayed message to end them at end time so their
+            // listeners can receive the corresponding callbacks.
+            anim->getVectorDrawable()->setPropertyChangeWillBeConsumed(false);
+        }
         // TODO: This is hacky
         info.windowInsetLeft = -stagingProperties().getLeft();
         info.windowInsetTop = -stagingProperties().getTop();
@@ -169,16 +205,46 @@
         info.windowInsetLeft = 0;
         info.windowInsetTop = 0;
         info.errorHandler = nullptr;
+
+        for (auto it = mVectorDrawableAnimators.begin(); it != mVectorDrawableAnimators.end();) {
+            if (!(*it)->getVectorDrawable()->getPropertyChangeWillBeConsumed()) {
+                // Vector Drawable is not in the display list, we should remove this animator from
+                // the list and post a delayed message to end the animator.
+                detachVectorDrawableAnimator(it->get());
+                it = mVectorDrawableAnimators.erase(it);
+            } else {
+                ++it;
+            }
+        }
+        info.out.hasAnimations |= !mVectorDrawableAnimators.empty();
     }
 
     void sendMessage(const sp<MessageHandler>& handler) {
         mLooper->sendMessage(handler, 0);
     }
 
+    void sendMessageDelayed(const sp<MessageHandler>& handler, nsecs_t delayInMs) {
+        mLooper->sendMessageDelayed(ms2ns(delayInMs), handler, 0);
+    }
+
     void attachAnimatingNode(RenderNode* animatingNode) {
         mPendingAnimatingRenderNodes.push_back(animatingNode);
     }
 
+    void attachPendingVectorDrawableAnimators() {
+        mVectorDrawableAnimators.insert(mPendingVectorDrawableAnimators.begin(),
+                mPendingVectorDrawableAnimators.end());
+        mPendingVectorDrawableAnimators.clear();
+    }
+
+    void detachAnimators() {
+        // Remove animators from the list and post a delayed message in future to end the animator
+        for (auto& anim : mVectorDrawableAnimators) {
+            detachVectorDrawableAnimator(anim.get());
+        }
+        mVectorDrawableAnimators.clear();
+    }
+
     void doAttachAnimatingNodes(AnimationContext* context) {
         for (size_t i = 0; i < mPendingAnimatingRenderNodes.size(); i++) {
             RenderNode* node = mPendingAnimatingRenderNodes[i].get();
@@ -187,17 +253,57 @@
         mPendingAnimatingRenderNodes.clear();
     }
 
+    void runVectorDrawableAnimators(AnimationContext* context) {
+        for (auto it = mVectorDrawableAnimators.begin(); it != mVectorDrawableAnimators.end();) {
+            (*it)->pushStaging(*context);
+            if ((*it)->animate(*context)) {
+                it = mVectorDrawableAnimators.erase(it);
+            } else {
+                ++it;
+            }
+        }
+    }
+
     void destroy() {
         for (auto& renderNode : mPendingAnimatingRenderNodes) {
             renderNode->animators().endAllStagingAnimators();
         }
         mPendingAnimatingRenderNodes.clear();
+        mPendingVectorDrawableAnimators.clear();
+    }
+
+    void addVectorDrawableAnimator(PropertyValuesAnimatorSet* anim) {
+        mPendingVectorDrawableAnimators.insert(anim);
     }
 
 private:
     sp<Looper> mLooper;
     JavaVM* mVm;
     std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes;
+    std::set< sp<PropertyValuesAnimatorSet> > mPendingVectorDrawableAnimators;
+    std::set< sp<PropertyValuesAnimatorSet> > mVectorDrawableAnimators;
+    void detachVectorDrawableAnimator(PropertyValuesAnimatorSet* anim) {
+        if (anim->isInfinite() || !anim->isRunning()) {
+            // Do not need to post anything if the animation is infinite (i.e. no meaningful
+            // end listener action), or if the animation has already ended.
+            return;
+        }
+        nsecs_t remainingTimeInMs = anim->getRemainingPlayTime();
+        // Post a delayed onFinished event that is scheduled to be handled when the animator ends.
+        if (anim->getOneShotListener()) {
+            // VectorDrawable's oneshot listener is updated when there are user triggered animation
+            // lifecycle changes, such as start(), end(), etc. By using checking and clearing
+            // one shot listener, we ensure the same end listener event gets posted only once.
+            // Therefore no duplicates. Another benefit of using one shot listener is that no
+            // removal is necessary: the end time of animation will not change unless triggered by
+            // user events, in which case the already posted listener's id will become stale, and
+            // the onFinished callback will then be ignored.
+            sp<FinishAndInvokeListener> message
+                    = new FinishAndInvokeListener(anim);
+            sendMessageDelayed(message, remainingTimeInMs);
+            anim->clearOneShotListener();
+        }
+    }
 };
 
 class AnimationContextBridge : public AnimationContext {
@@ -213,8 +319,16 @@
     virtual void startFrame(TreeInfo::TraversalMode mode) {
         if (mode == TreeInfo::MODE_FULL) {
             mRootNode->doAttachAnimatingNodes(this);
+            mRootNode->attachPendingVectorDrawableAnimators();
         }
         AnimationContext::startFrame(mode);
+        // Run VectorDrawable animators in the beginning of the frame instead of during prepareTree,
+        // because one VD can be in multiple render nodes' display list. So it's more simple to
+        // run them all at once before prepareTree than running them or checking whether they have
+        // already ran in each RenderNode. Note that these animators don't damage the RenderNodes.
+        // The damaging is done in prepareTree as needed after checking whether a VD has been
+        // modified.
+        mRootNode->runVectorDrawableAnimators(this);
     }
 
     // Runs any animations still left in mCurrentFrameAnimations
@@ -223,6 +337,10 @@
         postOnFinishedEvents();
     }
 
+    virtual void detachAnimators() override {
+        mRootNode->detachAnimators();
+    }
+
     virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) {
         OnFinishedEvent event(animator, listener);
         mOnFinishedEvents.push_back(event);
@@ -230,6 +348,7 @@
 
     virtual void destroy() {
         AnimationContext::destroy();
+        detachAnimators();
         postOnFinishedEvents();
     }
 
@@ -527,6 +646,13 @@
     rootRenderNode->attachAnimatingNode(animatingNode);
 }
 
+static void android_view_ThreadedRenderer_registerVectorDrawableAnimator(JNIEnv* env, jobject clazz,
+        jlong rootNodePtr, jlong animatorPtr) {
+    RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
+    PropertyValuesAnimatorSet* animator = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorPtr);
+    rootRenderNode->addVectorDrawableAnimator(animator);
+}
+
 static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
         jlong functorPtr, jboolean waitForCompletion) {
     Functor* functor = reinterpret_cast<Functor*>(functorPtr);
@@ -738,6 +864,7 @@
     { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
     { "nDestroy", "(JJ)V", (void*) android_view_ThreadedRenderer_destroy },
     { "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
+    { "nRegisterVectorDrawableAnimator", "(JJ)V", (void*) android_view_ThreadedRenderer_registerVectorDrawableAnimator },
     { "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
     { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer },
     { "nBuildLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_buildLayer },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e9a3409..3783dc8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -469,12 +469,6 @@
     <protected-broadcast android:name="android.net.wifi.PASSPOINT_ICON_RECEIVED" />
     <protected-broadcast android:name="com.android.server.notification.CountdownConditionProvider" />
 
-    <protected-broadcast android:name="com.android.ims.IMS_SERVICE_UP" />
-    <protected-broadcast android:name="com.android.ims.IMS_INCOMING_CALL" />
-    <protected-broadcast android:name="com.android.ims.internal.uce.UCE_SERVICE_UP" />
-    <protected-broadcast android:name="com.android.intent.action.IMS_FEATURE_CHANGED" />
-    <protected-broadcast android:name="com.android.intent.action.IMS_CONFIG_CHANGED" />
-
     <protected-broadcast android:name="com.android.internal.location.ALARM_WAKEUP" />
     <protected-broadcast android:name="com.android.internal.location.ALARM_TIMEOUT" />
     <protected-broadcast android:name="android.intent.action.GLOBAL_BUTTON" />
@@ -483,6 +477,8 @@
     <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
     <protected-broadcast android:name="com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK" />
 
+    <protected-broadcast android:name="com.android.server.am.ACTION_RESET_DEMO" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/core/res/res/drawable-watch/dialog_background_material.xml b/core/res/res/drawable-watch/dialog_background_material.xml
new file mode 100644
index 0000000..de52f08
--- /dev/null
+++ b/core/res/res/drawable-watch/dialog_background_material.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="?attr/colorBackground" />
+</shape>
diff --git a/core/res/res/layout-notround-watch/alert_dialog_header_micro.xml b/core/res/res/layout-notround-watch/alert_dialog_header_micro.xml
new file mode 100644
index 0000000..fc840d9
--- /dev/null
+++ b/core/res/res/layout-notround-watch/alert_dialog_header_micro.xml
@@ -0,0 +1,41 @@
+<?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
+  -->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:gravity="top|center_horizontal"
+        android:minHeight="@dimen/alert_dialog_title_height">
+    <ImageView android:id="@+id/icon"
+            android:maxHeight="24dp"
+            android:maxWidth="24dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginBottom="8dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@null" />
+    <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
+            style="?android:attr/windowTitleStyle"
+            android:ellipsize="end"
+            android:layout_marginStart="8dp"
+            android:layout_marginBottom="8dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart" />
+</LinearLayout>
diff --git a/core/res/res/layout-round-watch/alert_dialog_header_micro.xml b/core/res/res/layout-round-watch/alert_dialog_header_micro.xml
new file mode 100644
index 0000000..6f7ae02
--- /dev/null
+++ b/core/res/res/layout-round-watch/alert_dialog_header_micro.xml
@@ -0,0 +1,38 @@
+<?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
+  -->
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="top|center_horizontal"
+        android:minHeight="@dimen/alert_dialog_title_height">
+    <ImageView android:id="@+id/icon"
+            android:maxHeight="24dp"
+            android:maxWidth="24dp"
+            android:layout_marginTop="12dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@null" />
+    <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
+            style="?android:attr/windowTitleStyle"
+            android:ellipsize="end"
+            android:layout_marginTop="36dp"
+            android:layout_marginBottom="4dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="center" />
+</FrameLayout>
diff --git a/core/res/res/layout-watch/alert_dialog_material.xml b/core/res/res/layout-watch/alert_dialog_material.xml
new file mode 100644
index 0000000..e627d42
--- /dev/null
+++ b/core/res/res/layout-watch/alert_dialog_material.xml
@@ -0,0 +1,113 @@
+<?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
+  -->
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/parentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <ScrollView
+            android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+        <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            <!-- Top Panel -->
+            <FrameLayout
+                    android:paddingLeft="?dialogPreferredPadding"
+                    android:paddingRight="?dialogPreferredPadding"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/topPanel">
+                <include android:id="@+id/title_template"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        layout="@layout/alert_dialog_header_micro"/>
+            </FrameLayout>
+
+            <!-- Content Panel -->
+            <FrameLayout android:id="@+id/contentPanel"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:clipToPadding="false">
+                <TextView android:id="@+id/message"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="@style/TextAppearance.Material.Body1"
+                        android:paddingStart="?dialogPreferredPadding"
+                        android:paddingEnd="?dialogPreferredPadding"
+                        android:paddingTop="8dip"
+                        android:paddingBottom="8dip"/>
+            </FrameLayout>
+
+            <!-- Custom Panel, to replace content panel if needed -->
+            <FrameLayout android:id="@+id/customPanel"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:minHeight="64dp">
+                <FrameLayout android:id="@+android:id/custom"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content" />
+            </FrameLayout>
+
+            <!-- Button Panel -->
+            <FrameLayout
+                    android:id="@+id/buttonPanel"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:divider="?android:attr/dividerHorizontal"
+                        android:showDividers="beginning"
+                        android:dividerPadding="0dip"
+                        android:orientation="vertical"
+                        android:minHeight="@dimen/alert_dialog_button_bar_height"
+                        android:paddingBottom="?dialogPreferredPadding"
+                        style="?android:attr/buttonBarStyle"
+                        android:layoutDirection="locale"
+                        android:measureWithLargestChild="true">
+                    <Button android:id="@+id/button1"
+                            android:layout_gravity="start"
+                            android:layout_weight="1"
+                            android:layout_marginLeft="?dialogPreferredPadding"
+                            android:layout_marginRight="?dialogPreferredPadding"
+                            style="?android:attr/buttonBarButtonStyle"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content" />
+                    <Button android:id="@+id/button3"
+                            android:layout_gravity="start"
+                            android:layout_weight="1"
+                            android:layout_marginLeft="?dialogPreferredPadding"
+                            android:layout_marginRight="?dialogPreferredPadding"
+                            style="?android:attr/buttonBarButtonStyle"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content" />
+                    <Button android:id="@+id/button2"
+                            android:layout_gravity="start"
+                            android:layout_weight="1"
+                            android:layout_marginLeft="?dialogPreferredPadding"
+                            android:layout_marginRight="?dialogPreferredPadding"
+                            style="?android:attr/buttonBarButtonStyle"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content" />
+                </LinearLayout>
+            </FrameLayout>
+        </LinearLayout>
+    </ScrollView>
+</FrameLayout>
diff --git a/core/res/res/layout-watch/input_method_extract_view.xml b/core/res/res/layout-watch/input_method_extract_view.xml
index e3cd2ce..038b766 100644
--- a/core/res/res/layout-watch/input_method_extract_view.xml
+++ b/core/res/res/layout-watch/input_method_extract_view.xml
@@ -31,10 +31,10 @@
         android:inputType="text"
         android:layout_weight="1"
         android:fontFamily="sans-serif-condensed-light"
-        android:textColor="@color/primary_text_default_material_dark"
+        android:textColor="@color/primary_text_material_dark"
+        android:textColorHint="@color/secondary_text_material_dark"
         android:textColorHighlight="@color/accent_material_dark"
         android:textSize="18dp"
-        android:cursorVisible="false"
         android:gravity="bottom|right"
         />
 
diff --git a/core/res/res/layout/number_picker_with_selector_wheel_micro.xml b/core/res/res/layout-watch/number_picker_material.xml
similarity index 100%
rename from core/res/res/layout/number_picker_with_selector_wheel_micro.xml
rename to core/res/res/layout-watch/number_picker_material.xml
diff --git a/core/res/res/layout-watch/preference_material.xml b/core/res/res/layout-watch/preference_material.xml
new file mode 100644
index 0000000..5da64fc
--- /dev/null
+++ b/core/res/res/layout-watch/preference_material.xml
@@ -0,0 +1,82 @@
+<?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.
+-->
+
+<!-- Layout for a Preference in a PreferenceActivity. The
+     Preference is able to place a specific widget for its particular
+     type in the "widget_frame" layout. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+    android:background="?attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-4dp"
+        android:minWidth="32dp"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="8dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <com.android.internal.widget.PreferenceImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="24dp"
+            android:maxHeight="24dp" />
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+
+        <TextView android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="2"
+            android:textAppearance="?attr/textAppearanceListItem"
+            android:ellipsize="end" />
+
+        <TextView android:id="@+id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_alignStart="@id/title"
+            android:textAppearance="?attr/textAppearanceListItemSecondary"
+            android:textColor="?attr/textColorSecondary"
+            android:maxLines="10" />
+
+    </RelativeLayout>
+
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout android:id="@+id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="end|center_vertical"
+        android:paddingStart="4dp"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/alert_dialog_micro.xml b/core/res/res/layout/alert_dialog_micro.xml
deleted file mode 100644
index abdbd16..0000000
--- a/core/res/res/layout/alert_dialog_micro.xml
+++ /dev/null
@@ -1,140 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2014 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
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/parentPanel"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@android:color/white"
-    android:layout_gravity="center"
-    android:orientation="vertical">
-
-    <LinearLayout android:id="@+id/topPanel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-        <View android:id="@+id/titleDividerTop"
-            android:layout_width="match_parent"
-            android:layout_height="2dip"
-            android:visibility="gone"
-            android:background="@android:color/holo_blue_light" />
-        <LinearLayout android:id="@+id/title_template"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:gravity="center_vertical|start"
-            android:minHeight="@dimen/alert_dialog_title_height"
-            android:layout_marginStart="16dip"
-            android:layout_marginEnd="16dip">
-            <ImageView android:id="@+id/icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingEnd="8dip"
-                android:src="@null" />
-            <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
-                style="?android:attr/windowTitleStyle"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:textAlignment="viewStart" />
-        </LinearLayout>
-        <View android:id="@+id/titleDivider"
-            android:layout_width="match_parent"
-            android:layout_height="2dip"
-            android:visibility="gone"
-            android:background="@android:color/holo_blue_light" />
-        <!-- If the client uses a customTitle, it will be added here. -->
-    </LinearLayout>
-
-    <LinearLayout android:id="@+id/contentPanel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:minHeight="64dp">
-        <ScrollView android:id="@+id/scrollView"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:clipToPadding="false">
-            <TextView android:id="@+id/message"
-                style="?android:attr/textAppearanceMedium"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingStart="16dip"
-                android:paddingEnd="16dip"
-                android:paddingTop="8dip"
-                android:paddingBottom="8dip"/>
-        </ScrollView>
-    </LinearLayout>
-
-    <FrameLayout android:id="@+id/customPanel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:minHeight="64dp">
-        <FrameLayout android:id="@+android:id/custom"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-    </FrameLayout>
-
-    <LinearLayout android:id="@+id/buttonPanel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="@dimen/alert_dialog_button_bar_height"
-        android:orientation="vertical"
-        android:divider="?android:attr/dividerHorizontal"
-        android:showDividers="beginning"
-        android:dividerPadding="0dip">
-        <LinearLayout
-            style="?android:attr/buttonBarStyle"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:layoutDirection="locale"
-            android:measureWithLargestChild="true">
-            <Button android:id="@+id/button2"
-                android:layout_width="wrap_content"
-                android:layout_gravity="start"
-                android:layout_weight="1"
-                android:maxLines="2"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content" />
-            <Button android:id="@+id/button3"
-                android:layout_width="wrap_content"
-                android:layout_gravity="center_horizontal"
-                android:layout_weight="1"
-                android:maxLines="2"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content" />
-            <Button android:id="@+id/button1"
-                android:layout_width="wrap_content"
-                android:layout_gravity="end"
-                android:layout_weight="1"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:layout_height="wrap_content" />
-        </LinearLayout>
-     </LinearLayout>
-</LinearLayout>
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index fc53a1a..c43975e 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -33,8 +33,6 @@
             style="?attr/preferenceFragmentListStyle"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:paddingTop="0dip"
-            android:paddingBottom="@dimen/preference_fragment_padding_bottom"
             android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
             android:clipToPadding="false"
             android:drawSelectorOnTop="false"
diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml
index e411c0e..db2fe7d 100644
--- a/core/res/res/layout/preference_list_fragment_material.xml
+++ b/core/res/res/layout/preference_list_fragment_material.xml
@@ -32,8 +32,6 @@
         <ListView android:id="@android:id/list"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:paddingTop="0dip"
-            android:paddingBottom="@dimen/preference_fragment_padding_bottom"
             style="?attr/preferenceFragmentListStyle"
             android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
             android:clipToPadding="false"
diff --git a/core/res/res/values-watch/dimens.xml b/core/res/res/values-notround-watch/dimens.xml
similarity index 100%
rename from core/res/res/values-watch/dimens.xml
rename to core/res/res/values-notround-watch/dimens.xml
diff --git a/core/res/res/values-notround-watch/dimens_material.xml b/core/res/res/values-notround-watch/dimens_material.xml
new file mode 100644
index 0000000..b02437b
--- /dev/null
+++ b/core/res/res/values-notround-watch/dimens_material.xml
@@ -0,0 +1,23 @@
+<?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>
+    <dimen name="dialog_padding_material">8dp</dimen>
+    <dimen name="preference_fragment_padding_vertical_material">0dp</dimen>
+
+    <dimen name="list_item_padding_horizontal_material">16dp</dimen>
+    <dimen name="list_item_padding_start_material">16dp</dimen>
+    <dimen name="list_item_padding_end_material">16dp</dimen>
+</resources>
diff --git a/core/res/res/values-notround-watch/styles_material.xml b/core/res/res/values-notround-watch/styles_material.xml
new file mode 100644
index 0000000..cd8521f4
--- /dev/null
+++ b/core/res/res/values-notround-watch/styles_material.xml
@@ -0,0 +1,18 @@
+<?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>
+    <style name="TextAppearance.Material.AlertDialogMessage" parent="TextAppearance.Material.Body1"/>
+</resources>
diff --git a/core/res/res/values-round-watch/config_material.xml b/core/res/res/values-round-watch/config_material.xml
new file mode 100644
index 0000000..bf445ef
--- /dev/null
+++ b/core/res/res/values-round-watch/config_material.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for watch products.  Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Don't clip on round screens so the list can scroll past the rounded edges. -->
+    <bool name="config_preferenceFragmentClipToPadding">false</bool>
+</resources>
diff --git a/core/res/res/values-round-watch/dimens_material.xml b/core/res/res/values-round-watch/dimens_material.xml
new file mode 100644
index 0000000..a417d19
--- /dev/null
+++ b/core/res/res/values-round-watch/dimens_material.xml
@@ -0,0 +1,23 @@
+<?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>
+    <dimen name="dialog_padding_material">32dp</dimen>
+    <dimen name="preference_fragment_padding_vertical_material">22dp</dimen>
+
+    <dimen name="list_item_padding_horizontal_material">32dp</dimen>
+    <dimen name="list_item_padding_start_material">40dp</dimen>
+    <dimen name="list_item_padding_end_material">24dp</dimen>
+</resources>
diff --git a/core/res/res/values-round-watch/styles_material.xml b/core/res/res/values-round-watch/styles_material.xml
new file mode 100644
index 0000000..a2f3c02
--- /dev/null
+++ b/core/res/res/values-round-watch/styles_material.xml
@@ -0,0 +1,20 @@
+<?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>
+    <style name="TextAppearance.Material.AlertDialogMessage" parent="TextAppearance.Material.Body1">
+        <item name="textAlignment">center</item>
+    </style>
+</resources>
diff --git a/core/res/res/values-watch/colors_material.xml b/core/res/res/values-watch/colors_material.xml
new file mode 100644
index 0000000..cbdf879
--- /dev/null
+++ b/core/res/res/values-watch/colors_material.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2014 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>
+    <color name="background_material_dark">@color/micro_blue_grey_b15</color>
+    <color name="background_floating_material_dark">@color/micro_blue_grey_b30</color>
+
+    <color name="primary_material_dark">@color/micro_blue_grey_b65</color>
+    <color name="primary_dark_material_dark">@color/micro_blue_grey_b40</color>
+
+    <color name="accent_material_dark">@color/micro_blue_grey_b100</color>
+    <color name="accent_material_light">@color/micro_blue_grey_500</color>
+
+    <!-- Primary & accent colors -->
+    <eat-comment />
+
+    <!-- App color -->
+    <color name="micro_blue_grey_500">#ff607d8b</color>
+    <!-- Accent -->
+    <color name="micro_blue_grey_b100">#ffb0e5ff</color>
+    <!-- Lighter UI element -->
+    <color name="micro_blue_grey_b65">#ff7295a6</color>
+    <!-- UI Element -->
+    <color name="micro_blue_grey_b40">#ff465b66</color>
+    <!-- Lighter background -->
+    <color name="micro_blue_grey_b30">#ff35454d</color>
+    <!-- Dark background -->
+    <color name="micro_blue_grey_b15">#ff1a2226</color>
+
+    <!-- Button colors -->
+    <eat-comment />
+
+    <color name="micro_confirm_green">#ff4fc0b0</color>
+    <color name="micro_button_gray">#ffc2c2c2</color>
+    <color name="micro_action_blue">#ff0288d1</color>
+</resources>
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
new file mode 100644
index 0000000..81b53e7
--- /dev/null
+++ b/core/res/res/values-watch/config_material.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for watch products.  Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Watch type devices have limited screen real-estate, and thus action bars should not be
+         used. -->
+    <bool name="config_windowActionBarSupported">false</bool>
+
+    <!-- Watch type devices have limited screen real-estate, and thus titles should not be used. -->
+    <bool name="config_windowNoTitleDefault">true</bool>
+
+    <!-- Use micro alert controller -->
+    <integer name="config_alertDialogController">1</integer>
+
+    <!-- Always overscan by default to ensure onApplyWindowInsets will always be called. -->
+    <bool name="config_windowOverscanByDefault">true</bool>
+
+    <!-- Due to the smaller screen size, have dialog titles occupy more than 1 line. -->
+    <integer name="config_dialogWindowTitleMaxLines">3</integer>
+</resources>
diff --git a/core/res/res/values-watch/dimens_material.xml b/core/res/res/values-watch/dimens_material.xml
new file mode 100644
index 0000000..d579434
--- /dev/null
+++ b/core/res/res/values-watch/dimens_material.xml
@@ -0,0 +1,18 @@
+<?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>
+    <item name="text_line_spacing_multiplier_material" format="float" type="dimen">1.2</item>
+</resources>
diff --git a/core/res/res/values-watch/donottranslate_material.xml b/core/res/res/values-watch/donottranslate_material.xml
new file mode 100644
index 0000000..a6f2ff4
--- /dev/null
+++ b/core/res/res/values-watch/donottranslate_material.xml
@@ -0,0 +1,30 @@
+<?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>
+    <string name="font_family_display_4_material">sans-serif-condensed-light</string>
+    <string name="font_family_display_3_material">sans-serif-condensed-light</string>
+    <string name="font_family_display_2_material">sans-serif-condensed-light</string>
+    <string name="font_family_display_1_material">sans-serif-condensed-light</string>
+    <string name="font_family_headline_material">sans-serif-condensed-light</string>
+    <string name="font_family_title_material">sans-serif-condensed</string>
+    <string name="font_family_subhead_material">sans-serif-condensed-light</string>
+    <string name="font_family_menu_material">sans-serif-condensed-light</string>
+    <string name="font_family_body_2_material">sans-serif-condensed</string>
+    <string name="font_family_body_1_material">sans-serif-condensed-light</string>
+    <string name="font_family_caption_material">sans-serif-condensed-light</string>
+    <string name="font_family_button_material">sans-serif-condensed</string>
+ </resources>
diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
new file mode 100644
index 0000000..c19cc72
--- /dev/null
+++ b/core/res/res/values-watch/styles_material.xml
@@ -0,0 +1,83 @@
+<?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.
+-->
+
+<!--
+===============================================================
+                        PLEASE READ
+===============================================================
+
+The Material themes must not be modified in order to pass CTS.
+Many related themes and styles depend on other values defined in this file.
+If you would like to provide custom themes and styles for your device,
+please see styles_device_defaults.xml.
+
+===============================================================
+                        PLEASE READ
+===============================================================
+ -->
+<resources>
+    <style name="Animation.Material.Activity" parent="Animation.Activity">
+        <item name="activityOpenEnterAnimation">@anim/slide_in_enter_micro</item>
+        <item name="activityOpenRemoteViewsEnterAnimation">@anim/slide_in_enter_micro</item>
+        <item name="activityOpenExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="activityCloseEnterAnimation">@null</item>
+        <item name="activityCloseExitAnimation">@anim/slide_out_micro</item>
+        <item name="taskOpenEnterAnimation">@anim/slide_in_enter_micro</item>
+        <item name="taskOpenExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="taskCloseEnterAnimation">@null</item>
+        <item name="taskCloseExitAnimation">@anim/slide_out_micro</item>
+        <item name="taskToFrontEnterAnimation">@anim/slide_in_enter_micro</item>
+        <item name="taskToFrontExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="taskToBackEnterAnimation">@null</item>
+        <item name="taskToBackExitAnimation">@anim/slide_out_micro</item>
+        <item name="wallpaperOpenEnterAnimation">@null</item>
+        <item name="wallpaperOpenExitAnimation">@anim/slide_out_micro</item>
+        <item name="wallpaperCloseEnterAnimation">@anim/slide_in_enter_micro</item>
+        <item name="wallpaperCloseExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="wallpaperIntraOpenEnterAnimation">@null</item>
+        <item name="wallpaperIntraOpenExitAnimation">@anim/slide_out_micro</item>
+        <item name="wallpaperIntraCloseEnterAnimation">@anim/slide_in_enter_micro</item>
+        <item name="wallpaperIntraCloseExitAnimation">@anim/slide_in_exit_micro</item>
+    </style>
+
+    <style name="Widget.Material.TextView" parent="Widget.TextView">
+        <item name="breakStrategy">balanced</item>
+    </style>
+
+    <!-- Alert dialog button bar button -->
+    <style name="Widget.Material.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.Borderless.Small">
+        <item name="gravity">center_vertical|left</item>
+        <item name="minWidth">64dp</item>
+        <item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
+    </style>
+
+    <style name="Widget.Material.NumberPicker" parent="Widget.NumberPicker">
+        <item name="internalLayout">@layout/number_picker_material</item>
+        <item name="solidColor">@color/transparent</item>
+        <item name="selectionDivider">@drawable/numberpicker_selection_divider</item>
+        <item name="selectionDividerHeight">2dp</item>
+        <item name="selectionDividersDistance">48dp</item>
+        <item name="internalMinWidth">64dp</item>
+        <item name="internalMaxHeight">180dp</item>
+        <item name="virtualButtonPressedDrawable">?selectableItemBackground</item>
+        <item name="descendantFocusability">blocksDescendants</item>
+    </style>
+
+    <!-- DO NOTE TRANSLATE Spans within this text are applied to style composing regions
+    within an EditText widget. The text content is ignored and not used.
+    Note: This is @color/material_deep_teal_200, cannot use @color references here. -->
+    <string name="candidates_style"><font color="#80cbc4">candidates</font></string>
+</resources>
diff --git a/core/res/res/values-watch/themes.xml b/core/res/res/values-watch/themes.xml
index 6d6065f..04df180 100644
--- a/core/res/res/values-watch/themes.xml
+++ b/core/res/res/values-watch/themes.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<?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.
@@ -14,11 +14,10 @@
      limitations under the License.
 -->
 <resources>
-    <style name="Theme.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
-    <style name="Theme.Dialog.AppError" parent="Theme.Micro.Dialog.AppError" />
-    <style name="Theme.Holo.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
-    <style name="Theme.Holo.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
-    <style name="Theme.InputMethod" parent="Theme.Micro.InputMethod" />
-    <style name="Theme.Material.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
-    <style name="Theme.Material.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
+    <!-- Theme for the dialog shown when an app crashes or ANRs. Override to make it dark. -->
+    <style name="Theme.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert">
+        <item name="windowContentTransitions">false</item>
+        <item name="windowActivityTransitions">false</item>
+        <item name="windowCloseOnTouchOutside">false</item>
+    </style>
 </resources>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 66509fb..2313b26 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -13,22 +13,31 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <style name="Theme.DeviceDefault" parent="Theme.Micro" />
-    <style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Micro" />
-    <style name="Theme.DeviceDefault.Dialog" parent="Theme.Micro.Dialog" />
-    <style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Micro.Dialog" />
-    <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
-    <style name="Theme.DeviceDefault.InputMethod" parent="Theme.Micro.InputMethod"  />
-    <style name="Theme.DeviceDefault.Panel" parent="Theme.Micro.Panel"  />
-    <style name="Theme.DeviceDefault.Light" parent="Theme.Micro.Light" />
-    <style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Micro.Light" />
-    <style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.Micro.Light" />
-    <style name="Theme.DeviceDefault.Light.Dialog" parent="Theme.Micro.Dialog" />
-    <style name="Theme.DeviceDefault.Light.DialogWhenLarge" parent="Theme.Micro.Dialog" />
-    <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
-    <style name="Theme.DeviceDefault.Light.Panel" parent="Theme.Micro.Light.Panel"  />
-    <style name="Theme.DeviceDefault.Settings" parent="Theme.Micro" />
-    <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Micro" />
-</resources>
 
+<!--
+===============================================================
+                        PLEASE READ
+===============================================================
+This file contains the themes that are the Device Defaults.
+If you want to edit themes to skin your device, do it here.
+We recommend that you do not edit themes.xml and instead edit
+this file.
+
+Editing this file instead of themes.xml will greatly simplify
+merges for future platform versions and CTS compliance will be
+easier.
+===============================================================
+                        PLEASE READ
+===============================================================
+ -->
+<resources>
+    <!-- Theme used for the intent picker activity. -->
+    <style name="Theme.DeviceDefault.Resolver" parent="Theme.Material">
+        <item name="colorControlActivated">?attr/colorControlHighlight</item>
+        <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
+    </style>
+
+    <!-- Use a dark theme for watches. -->
+    <style name="Theme.DeviceDefault.System" parent="Theme.Material" />
+</resources>
diff --git a/core/res/res/values-watch/themes_material.xml b/core/res/res/values-watch/themes_material.xml
new file mode 100644
index 0000000..4ae4367
--- /dev/null
+++ b/core/res/res/values-watch/themes_material.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+
+<!--
+===============================================================
+                        PLEASE READ
+===============================================================
+
+The Material themes must not be modified in order to pass CTS.
+Many related themes and styles depend on other values defined in this file.
+If you would like to provide custom themes and styles for your device,
+please see styles_device_defaults.xml.
+
+===============================================================
+                        PLEASE READ
+===============================================================
+ -->
+<resources>
+    <!-- Default theme for material style input methods, which is used by the
+         {@link android.inputmethodservice.InputMethodService} class.
+         this inherits from Theme.Panel, but sets up IME appropriate animations
+         and a few custom attributes. -->
+    <style name="Theme.Material.InputMethod" parent="Theme.Material.Panel">
+        <item name="windowAnimationStyle">@style/Animation.InputMethod</item>
+        <item name="imeFullscreenBackground">?colorBackground</item>
+        <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
+    </style>
+
+    <!-- Override behaviour to set the theme colours for dialogs, keep them the same. -->
+    <style name="ThemeOverlay.Material.Dialog" parent="ThemeOverlay.Material.BaseDialog">
+        <item name="windowIsFloating">false</item>
+    </style>
+
+    <!-- Force the background and floating colours to be the default colours. -->
+    <style name="Theme.Material.Dialog" parent="Theme.Material.BaseDialog">
+        <item name="colorBackground">@color/background_material_dark</item>
+        <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
+        <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
+        <item name="windowIsFloating">false</item>
+    </style>
+
+    <!-- Force the background and floating colours to be the default colours. -->
+    <style name="Theme.Material.Light.Dialog" parent="Theme.Material.Light.BaseDialog">
+        <item name="colorBackground">@color/background_material_light</item>
+        <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
+        <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
+        <item name="windowIsFloating">false</item>
+    </style>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b61d6cf..d0c6a8e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1832,6 +1832,10 @@
         <enum name="KEYCODE_CUT" value="277" />
         <enum name="KEYCODE_COPY" value="278" />
         <enum name="KEYCODE_PASTE" value="279" />
+        <enum name="KEYCODE_FP_NAV_UP" value="280" />
+        <enum name="KEYCODE_FP_NAV_DOWN" value="281" />
+        <enum name="KEYCODE_FP_NAV_LEFT" value="282" />
+        <enum name="KEYCODE_FP_NAV_RIGHT" value="283" />
     </attr>
 
     <!-- ***************************************************************** -->
@@ -2043,6 +2047,13 @@
         <attr name="showTitle" format="boolean" />
         <!-- @hide Whether fullDark, etc. should use default values if null. -->
         <attr name="needsDefaultBackgrounds" format="boolean" />
+        <!-- @hide Workaround until we replace AlertController with custom layout. -->
+        <attr name="controllerType">
+            <!-- The default controller. -->
+            <enum name="normal" value="0" />
+            <!-- Controller for micro specific layout. -->
+            <enum name="micro" value="1" />
+        </attr>
     </declare-styleable>
 
     <!-- @hide -->
@@ -7324,14 +7335,24 @@
     <declare-styleable name="Wallpaper">
         <attr name="settingsActivity" />
 
-        <!-- Reference to a the wallpaper's thumbnail bitmap. -->
+        <!-- Reference to the wallpaper's thumbnail bitmap. -->
         <attr name="thumbnail" format="reference" />
 
-        <!-- Name of the author of this component, e.g. Google. -->
+        <!-- Name of the author and/or source/collection of this component, e.g. Art Collection, Picasso. -->
         <attr name="author" format="reference" />
 
         <!-- Short description of the component's purpose or behavior. -->
         <attr name="description" />
+
+        <!-- Uri that specifies a link for further context of this wallpaper, e.g. http://www.picasso.org. -->
+        <attr name="contextUri" format="reference" />
+
+        <!-- Title of the uri that specifies a link for further context of this wallpaper, e.g. Explore collection. -->
+        <attr name="contextDescription" format="reference" />
+
+        <!-- Whether to show any metadata when previewing the wallpaper. -->
+        <attr name="showMetadataInPreview" format="boolean" />
+
     </declare-styleable>
 
     <!-- Use <code>dream</code> as the root tag of the XML resource that
@@ -8226,4 +8247,16 @@
                    color. -->
         <attr name="colorBackground" />
     </declare-styleable>
+
+    <declare-styleable name="Shortcut">
+        <attr name="shortcutId" format="string" />
+        <attr name="enabled" />
+        <attr name="shortcutIcon" format="reference" />
+        <attr name="shortcutShortLabel" format="reference" />
+        <attr name="shortcutLongLabel" format="reference" />
+        <attr name="shortcutDisabledMessage" format="reference" />
+        <attr name="shortcutCategories" format="string" />
+        <attr name="shortcutIntentAction" format="string" />
+        <attr name="shortcutIntentData" format="string" />
+    </declare-styleable>
 </resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d69d73d..0872ef9 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -62,6 +62,20 @@
          a reference to a Drawable resource containing the image definition. -->
     <attr name="icon" format="reference" />
 
+    <!-- A Drawable resource providing a graphical representation of its
+         associated item.  Use with the
+         application tag (to supply a default round icon for all application
+         components), or with the activity, receiver, service, or instrumentation
+         tag (to supply a specific round icon for that component).  It may also be
+         used with the intent-filter tag to supply a round icon to show to the
+         user when an activity is being selected based on a particular Intent.
+
+         <p>The given round icon will be used to display to the user a graphical
+         representation of its associated component; for example, as the round icon
+         for main activity that is displayed in the launcher.  This must be
+         a reference to a Drawable resource containing the image definition. -->
+    <attr name="roundIcon" format="reference" />
+
     <!-- A Drawable resource providing an extended graphical banner for its
          associated item. Use with the application tag (to supply a default
          banner for all application activities), or with the activity, tag to
@@ -1236,6 +1250,7 @@
         <attr name="theme" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="description" />
@@ -1335,6 +1350,7 @@
         <attr name="name" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="permissionGroup" />
@@ -1362,6 +1378,7 @@
         <attr name="name" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="description" />
@@ -1395,6 +1412,7 @@
         <attr name="name" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
     </declare-styleable>
@@ -1676,6 +1694,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="process" />
@@ -1759,6 +1778,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="permission" />
@@ -1807,6 +1827,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="permission" />
@@ -1843,6 +1864,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="launchMode" />
@@ -1926,6 +1948,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="permission" />
@@ -1998,6 +2021,7 @@
          parent="AndroidManifestActivity AndroidManifestReceiver AndroidManifestService">
         <attr name="label" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="priority" />
@@ -2128,6 +2152,7 @@
         <attr name="targetPackage" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="roundIcon" />
         <attr name="banner" />
         <attr name="logo" />
         <attr name="handleProfiling" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 87cb2e8..78e6ed8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2489,4 +2489,13 @@
     <string-array translatable="false" name="config_defaultPinnerServiceFiles">
     </string-array>
 
+    <!-- Component that is the default launcher when demo mode is enabled. -->
+    <string name="config_demoModeLauncherComponent"></string>
+
+    <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
+    <bool name="config_useRoundIcon">false</bool>
+
+    <!-- True if the device supports system navigation keys. -->
+    <bool name="config_supportSystemNavigationKeys">false</bool>
+
 </resources>
diff --git a/core/res/res/values/config_material.xml b/core/res/res/values/config_material.xml
new file mode 100644
index 0000000..a37be83
--- /dev/null
+++ b/core/res/res/values/config_material.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds, only for Material theme.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue".  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- True if the device supports action bars. -->
+    <bool name="config_windowActionBarSupported">true</bool>
+
+    <!-- True if the device should have titles by default. -->
+    <bool name="config_windowNoTitleDefault">false</bool>
+
+    <!-- The alert controller to use for alert dialogs. -->
+    <integer name="config_alertDialogController">0</integer>
+
+    <!-- True if windowOverscan should be on by default. -->
+    <bool name="config_windowOverscanByDefault">false</bool>
+
+    <!-- Max number of lines for the dialog title. -->
+    <integer name="config_dialogWindowTitleMaxLines">1</integer>
+
+    <!-- True if preference fragment should clip to padding. -->
+    <bool name="config_preferenceFragmentClipToPadding">true</bool>
+</resources>
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index 00e48a0..f96cef9 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -24,6 +24,8 @@
 
     <!-- Preference fragment padding, sides -->
     <dimen name="preference_fragment_padding_side_material">0dp</dimen>
+    <!-- Preference fragment padding, vertical -->
+    <dimen name="preference_fragment_padding_vertical_material">0dp</dimen>
 
     <!-- Preference breadcrumbs padding, start padding -->
     <dimen name="preference_breadcrumbs_padding_start_material">12dp</dimen>
@@ -53,6 +55,8 @@
     <!-- Default padding for list items. This should match the action bar
          content inset so that ListActivity items line up correctly. -->
     <dimen name="list_item_padding_horizontal_material">@dimen/action_bar_content_inset_material</dimen>
+    <dimen name="list_item_padding_start_material">@dimen/action_bar_content_inset_material</dimen>
+    <dimen name="list_item_padding_end_material">@dimen/action_bar_content_inset_material</dimen>
 
     <!-- Padding to add to the start of the overflow action button. -->
     <dimen name="action_bar_overflow_padding_start_material">6dp</dimen>
@@ -84,6 +88,8 @@
     <dimen name="text_size_medium_material">18sp</dimen>
     <dimen name="text_size_small_material">14sp</dimen>
 
+    <item name="text_line_spacing_multiplier_material" format="float" type="dimen">1.0</item>
+
     <dimen name="text_edit_floating_toolbar_elevation">2dp</dimen>
     <dimen name="text_edit_floating_toolbar_margin">20dp</dimen>
 
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 7f8acd3..5c165e6 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -122,9 +122,11 @@
 
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_PROGRESS}. -->
   <item type="id" name="accessibilityActionSetProgress" />
-  
+
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
   <item type="id" name="accessibilityActionContextClick" />
 
   <item type="id" name="remote_input_tag" />
+
+  <item type="id" name="cross_task_transition" />
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6797799..da31767 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2728,4 +2728,22 @@
     <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="shortcutIcon" />
+    <public type="attr" name="shortcutShortLabel" />
+    <public type="attr" name="shortcutLongLabel" />
+    <public type="attr" name="shortcutDisabledMessage" />
+    <public type="attr" name="shortcutCategories" />
+    <public type="attr" name="shortcutIntentAction" />
+    <public type="attr" name="shortcutIntentData" />
+    <public type="attr" name="roundIcon" />
+
+    <public type="attr" name="contextUri" />
+    <public type="attr" name="contextDescription" />
+    <public type="attr" name="showMetadataInPreview" />
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 48744b6..5db562d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4366,6 +4366,17 @@
     <!-- The representation of a time duration when negative. An example is -1:14. This can be used with a countdown timer for example.-->
     <string name="negative_duration">\u2212<xliff:g id="time" example="1:14">%1$s</xliff:g></string>
 
+    <!-- Title of notification to start a new demo session when device is in retail mode [CHAR LIMIT=NONE] -->
+    <string name="reset_retail_demo_mode_title">Restart Session</string>
+    <!-- Text of notification to start a new demo session when device is in retail mode [CHAR LIMIT=NONE] -->
+    <string name="reset_retail_demo_mode_text">Tap to start a new demo session</string>
+    <!-- Text of dialog shown when starting a demo user for the first time [CHAR LIMIT=40] -->
+    <string name="demo_starting_message">Starting demo</string>
+    <!-- Text of dialog shown when starting a new demo user in retail demo mode [CHAR LIMIT=40] -->
+    <string name="demo_restarting_message">Restarting session</string>
+
+
+
     <!-- Title of notification shown when device has been forced to safe mode after a security compromise. -->
     <string name="audit_safemode_notification">Factory reset to use this device without restrictions</string>
     <!-- Description of notification shown when device has been forced to safe mode after a security compromise. -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 790dcfa..273086d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1122,6 +1122,8 @@
     <style name="PreferenceFragmentList">
         <item name="paddingStart">@dimen/preference_fragment_padding_side</item>
         <item name="paddingEnd">@dimen/preference_fragment_padding_side</item>
+        <item name="paddingTop">0dp</item>
+        <item name="paddingBottom">@dimen/preference_fragment_padding_bottom</item>
     </style>
 
     <!-- Other Misc Styles -->
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index bb07834..6e0ad36 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -40,7 +40,10 @@
         <item name="layout">@layout/preference_list_fragment_material</item>
         <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item>
         <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item>
+        <item name="paddingTop">@dimen/preference_fragment_padding_vertical_material</item>
+        <item name="paddingBottom">@dimen/preference_fragment_padding_vertical_material</item>
         <item name="divider">?attr/listDivider</item>
+        <item name="clipToPadding">@bool/config_preferenceFragmentClipToPadding</item>
     </style>
 
     <style name="PreferenceActivity.Material">
@@ -134,6 +137,8 @@
     <style name="PreferenceFragmentList.Material">
         <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item>
         <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item>
+        <item name="paddingTop">@dimen/preference_fragment_padding_vertical_material</item>
+        <item name="paddingBottom">@dimen/preference_fragment_padding_vertical_material</item>
     </style>
 
     <!-- Begin Material theme styles -->
@@ -147,6 +152,7 @@
         <item name="textColorLink">?attr/textColorLink</item>
         <item name="textSize">@dimen/text_size_body_1_material</item>
         <item name="fontFamily">@string/font_family_body_1_material</item>
+        <item name="lineSpacingMultiplier">@dimen/text_line_spacing_multiplier_material</item>
     </style>
 
     <style name="TextAppearance.Material.Display4">
@@ -1190,6 +1196,7 @@
         <item name="listItemLayout">@layout/select_dialog_item_material</item>
         <item name="multiChoiceItemLayout">@layout/select_dialog_multichoice_material</item>
         <item name="singleChoiceItemLayout">@layout/select_dialog_singlechoice_material</item>
+        <item name="controllerType">@integer/config_alertDialogController</item>
     </style>
 
     <style name="AlertDialog.Material.Light" />
@@ -1224,7 +1231,7 @@
     <style name="DialogWindowTitleBackground.Material.Light" />
 
     <style name="DialogWindowTitle.Material">
-        <item name="maxLines">1</item>
+        <item name="maxLines">@integer/config_dialogWindowTitleMaxLines</item>
         <item name="scrollHorizontally">true</item>
         <item name="textAppearance">@style/TextAppearance.Material.DialogWindowTitle</item>
     </style>
diff --git a/core/res/res/values/styles_micro.xml b/core/res/res/values/styles_micro.xml
deleted file mode 100644
index 341a0a4c..0000000
--- a/core/res/res/values/styles_micro.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2014 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>
-    <style name="Animation.Micro"/>
-
-    <style name="Animation.Micro.Activity" parent="Animation.Material.Activity">
-        <item name="activityOpenEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="activityOpenRemoteViewsEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="activityOpenExitAnimation">@anim/slide_in_exit_micro</item>
-        <item name="activityCloseEnterAnimation">@null</item>
-        <item name="activityCloseExitAnimation">@anim/slide_out_micro</item>
-        <item name="taskOpenEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="taskOpenExitAnimation">@anim/slide_in_exit_micro</item>
-        <item name="taskCloseEnterAnimation">@null</item>
-        <item name="taskCloseExitAnimation">@anim/slide_out_micro</item>
-        <item name="taskToFrontEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="taskToFrontExitAnimation">@anim/slide_in_exit_micro</item>
-        <item name="taskToBackEnterAnimation">@null</item>
-        <item name="taskToBackExitAnimation">@anim/slide_out_micro</item>
-        <item name="wallpaperOpenEnterAnimation">@null</item>
-        <item name="wallpaperOpenExitAnimation">@anim/slide_out_micro</item>
-        <item name="wallpaperCloseEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="wallpaperCloseExitAnimation">@anim/slide_in_exit_micro</item>
-        <item name="wallpaperIntraOpenEnterAnimation">@null</item>
-        <item name="wallpaperIntraOpenExitAnimation">@anim/slide_out_micro</item>
-        <item name="wallpaperIntraCloseEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="wallpaperIntraCloseExitAnimation">@anim/slide_in_exit_micro</item>
-    </style>
-
-    <style name="AlertDialog.Micro" parent="AlertDialog.Material.Light">
-        <item name="fullDark">@null</item>
-        <item name="topDark">@null</item>
-        <item name="centerDark">@null</item>
-        <item name="bottomDark">@null</item>
-        <item name="fullBright">@null</item>
-        <item name="topBright">@null</item>
-        <item name="centerBright">@null</item>
-        <item name="bottomBright">@null</item>
-        <item name="bottomMedium">@null</item>
-        <item name="centerMedium">@null</item>
-        <item name="layout">@layout/alert_dialog_micro</item>
-    </style>
-
-    <style name="DialogWindowTitle.Micro">
-        <item name="maxLines">1</item>
-        <item name="scrollHorizontally">true</item>
-        <item name="textAppearance">@style/TextAppearance.Micro.DialogWindowTitle</item>
-    </style>
-
-    <style name="TextAppearance.Micro" parent="TextAppearance.Material">
-        <item name="textSize">20sp</item>
-        <item name="fontFamily">sans-serif-condensed-light</item>
-        <item name="textColor">@color/micro_text_light</item>
-    </style>
-
-    <style name="TextAppearance.Micro.DialogWindowTitle" parent="TextAppearance.Material.DialogWindowTitle">
-        <item name="textSize">20sp</item>
-        <item name="fontFamily">sans-serif-condensed-light</item>
-        <item name="textColor">@color/micro_text_light</item>
-    </style>
-
-    <style name="Widget.Micro" parent="Widget.Material" />
-
-    <style name="Widget.Micro.TextView">
-        <item name="fontFamily">sans-serif-condensed</item>
-    </style>
-
-    <style name="Widget.Micro.NumberPicker">
-        <item name="internalLayout">@layout/number_picker_with_selector_wheel_micro</item>
-        <item name="solidColor">@color/transparent</item>
-        <item name="selectionDivider">@drawable/numberpicker_selection_divider</item>
-        <item name="selectionDividerHeight">0dip</item>
-        <item name="selectionDividersDistance">104dip</item>
-        <item name="internalMinWidth">64dip</item>
-        <item name="internalMaxHeight">180dip</item>
-        <item name="virtualButtonPressedDrawable">?attr/selectableItemBackground</item>
-        <item name="descendantFocusability">blocksDescendants</item>
-    </style>
-
-</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e9b57c..6e937ff 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1103,6 +1103,10 @@
   <java-symbol type="string" name="lockscreen_transport_pause_description" />
   <java-symbol type="string" name="config_ethernet_tcp_buffers" />
   <java-symbol type="string" name="config_wifi_tcp_buffers" />
+  <java-symbol type="string" name="config_demoModeLauncherComponent" />
+  <java-symbol type="string" name="demo_starting_message" />
+  <java-symbol type="string" name="demo_restarting_message" />
+
 
   <java-symbol type="plurals" name="bugreport_countdown" />
   <java-symbol type="plurals" name="duration_hours" />
@@ -1859,7 +1863,6 @@
   <java-symbol type="string" name="vpn_lockdown_config" />
   <java-symbol type="string" name="wallpaper_binding_label" />
   <java-symbol type="style" name="Theme.Dialog.AppError" />
-  <java-symbol type="style" name="Theme.Micro.Dialog.Alert" />
   <java-symbol type="style" name="Theme.Leanback.Dialog.Alert" />
   <java-symbol type="style" name="Theme.Toast" />
   <java-symbol type="xml" name="storage_list" />
@@ -1887,6 +1890,8 @@
   <java-symbol type="string" name="config_persistentDataPackageName" />
   <java-symbol type="string" name="audit_safemode_notification" />
   <java-symbol type="string" name="audit_safemode_notification_details" />
+  <java-symbol type="string" name="reset_retail_demo_mode_title" />
+  <java-symbol type="string" name="reset_retail_demo_mode_text" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
@@ -2183,6 +2188,7 @@
   <java-symbol type="style" name="TextAppearance.Material.TimePicker.TimeLabel" />
   <java-symbol type="attr" name="seekBarPreferenceStyle" />
   <java-symbol type="style" name="Theme.DeviceDefault.Resolver" />
+  <java-symbol type="style" name="Theme.DeviceDefault.System" />
   <java-symbol type="attr" name="preferenceActivityStyle" />
   <java-symbol type="attr" name="preferenceFragmentStyle" />
   <java-symbol type="bool" name="skipHoldBeforeMerge" />
@@ -2609,4 +2615,12 @@
   <java-symbol type="array" name="config_defaultPinnerServiceFiles" />
 
   <java-symbol type="string" name="suspended_widget_accessibility" />
+
+  <!-- Used internally for assistant to launch activity transitions -->
+  <java-symbol type="id" name="cross_task_transition" />
+
+  <java-symbol type="bool" name="config_useRoundIcon" />
+
+  <!-- For System navigation keys -->
+  <java-symbol type="bool" name="config_supportSystemNavigationKeys" />
 </resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 11bb106..ea1b626 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -551,4 +551,6 @@
         <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
     </style>
 
+    <!-- DeviceDefault theme for the default system theme.  -->
+    <style name="Theme.DeviceDefault.System" parent="Theme.Material.Light.DarkActionBar" />
 </resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 2ea5c5e..881b9b3 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -119,8 +119,8 @@
         <item name="textAppearanceListItemSecondary">@style/TextAppearance.Material.Body1</item>
         <item name="listPreferredItemPaddingLeft">@dimen/list_item_padding_horizontal_material</item>
         <item name="listPreferredItemPaddingRight">@dimen/list_item_padding_horizontal_material</item>
-        <item name="listPreferredItemPaddingStart">@dimen/list_item_padding_horizontal_material</item>
-        <item name="listPreferredItemPaddingEnd">@dimen/list_item_padding_horizontal_material</item>
+        <item name="listPreferredItemPaddingStart">@dimen/list_item_padding_start_material</item>
+        <item name="listPreferredItemPaddingEnd">@dimen/list_item_padding_end_material</item>
 
         <!-- @hide -->
         <item name="searchResultListItemHeight">58dip</item>
@@ -153,9 +153,9 @@
         <item name="windowBackground">?attr/colorBackground</item>
         <item name="windowClipToOutline">true</item>
         <item name="windowFrame">@null</item>
-        <item name="windowNoTitle">false</item>
+        <item name="windowNoTitle">@bool/config_windowNoTitleDefault</item>
         <item name="windowFullscreen">false</item>
-        <item name="windowOverscan">false</item>
+        <item name="windowOverscan">@bool/config_windowOverscanByDefault</item>
         <item name="windowIsFloating">false</item>
         <item name="windowContentOverlay">@null</item>
         <item name="windowShowWallpaper">false</item>
@@ -164,7 +164,7 @@
         <item name="windowTitleBackgroundStyle">@style/WindowTitleBackground.Material</item>
         <item name="windowAnimationStyle">@style/Animation.Material.Activity</item>
         <item name="windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
-        <item name="windowActionBar">true</item>
+        <item name="windowActionBar">@bool/config_windowActionBarSupported</item>
         <item name="windowActionModeOverlay">false</item>
         <item name="windowDrawsSystemBarBackgrounds">true</item>
         <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item>
@@ -480,8 +480,8 @@
         <item name="textAppearanceListItemSecondary">@style/TextAppearance.Material.Body1</item>
         <item name="listPreferredItemPaddingLeft">@dimen/list_item_padding_horizontal_material</item>
         <item name="listPreferredItemPaddingRight">@dimen/list_item_padding_horizontal_material</item>
-        <item name="listPreferredItemPaddingStart">@dimen/list_item_padding_horizontal_material</item>
-        <item name="listPreferredItemPaddingEnd">@dimen/list_item_padding_horizontal_material</item>
+        <item name="listPreferredItemPaddingStart">@dimen/list_item_padding_start_material</item>
+        <item name="listPreferredItemPaddingEnd">@dimen/list_item_padding_end_material</item>
 
         <!-- @hide -->
         <item name="searchResultListItemHeight">58dip</item>
@@ -514,9 +514,9 @@
         <item name="windowBackground">?attr/colorBackground</item>
         <item name="windowClipToOutline">true</item>
         <item name="windowFrame">@null</item>
-        <item name="windowNoTitle">false</item>
+        <item name="windowNoTitle">@bool/config_windowNoTitleDefault</item>
         <item name="windowFullscreen">false</item>
-        <item name="windowOverscan">false</item>
+        <item name="windowOverscan">@bool/config_windowOverscanByDefault</item>
         <item name="windowIsFloating">false</item>
         <item name="windowContentOverlay">@null</item>
         <item name="windowShowWallpaper">false</item>
@@ -525,7 +525,7 @@
         <item name="windowTitleBackgroundStyle">@style/WindowTitleBackground.Material</item>
         <item name="windowAnimationStyle">@style/Animation.Material.Activity</item>
         <item name="windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
-        <item name="windowActionBar">true</item>
+        <item name="windowActionBar">@bool/config_windowActionBarSupported</item>
         <item name="windowActionModeOverlay">false</item>
         <item name="windowDrawsSystemBarBackgrounds">true</item>
         <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item>
@@ -864,11 +864,8 @@
         <item name="searchViewStyle">@style/Widget.Material.SearchView.ActionBar</item>
     </style>
 
-    <!-- Theme overlay that overrides window properties to display as a dialog. -->
-    <style name="ThemeOverlay.Material.Dialog">
-        <item name="colorBackgroundCacheHint">@null</item>
-        <item name="colorBackground">?attr/colorBackgroundFloating</item>
-
+    <!-- Base theme for overlay dialogs, customize the colours in the actual dialog theme. -->
+    <style name="ThemeOverlay.Material.BaseDialog">
         <item name="windowFrame">@null</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
         <item name="windowTitleBackgroundStyle">@style/DialogWindowTitleBackground.Material</item>
@@ -897,6 +894,12 @@
         <item name="windowFixedHeightMinor">@null</item>
     </style>
 
+    <!-- Theme overlay that overrides window properties to display as a dialog. -->
+    <style name="ThemeOverlay.Material.Dialog" parent="ThemeOverlay.Material.BaseDialog">
+        <item name="colorBackgroundCacheHint">@null</item>
+        <item name="colorBackground">?attr/colorBackgroundFloating</item>
+    </style>
+
     <!-- Theme overlay that overrides window properties to display as a date picker dialog. -->
     <style name="ThemeOverlay.Material.Dialog.DatePicker">
         <item name="alertDialogStyle">@style/DatePickerDialog.Material</item>
@@ -1090,10 +1093,10 @@
 
         <item name="colorBackgroundCacheHint">@null</item>
 
-        <item name="listPreferredItemPaddingLeft">24dip</item>
-        <item name="listPreferredItemPaddingRight">24dip</item>
-        <item name="listPreferredItemPaddingStart">24dip</item>
-        <item name="listPreferredItemPaddingEnd">24dip</item>
+        <item name="listPreferredItemPaddingLeft">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingRight">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
 
         <item name="listDivider">@null</item>
 
@@ -1201,10 +1204,10 @@
 
         <item name="colorBackgroundCacheHint">@null</item>
 
-        <item name="listPreferredItemPaddingLeft">24dip</item>
-        <item name="listPreferredItemPaddingRight">24dip</item>
-        <item name="listPreferredItemPaddingStart">24dip</item>
-        <item name="listPreferredItemPaddingEnd">24dip</item>
+        <item name="listPreferredItemPaddingLeft">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingRight">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
 
         <item name="listDivider">@null</item>
 
diff --git a/core/res/res/values/themes_micro.xml b/core/res/res/values/themes_micro.xml
deleted file mode 100644
index 25a6e00..0000000
--- a/core/res/res/values/themes_micro.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2014 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>
-    <style name="Theme.MicroBase" parent="Theme.Material.NoActionBar">
-        <item name="alertDialogTheme">@style/Theme.Micro.Dialog.Alert</item>
-        <item name="alertDialogStyle">@style/AlertDialog.Micro</item>
-        <item name="dialogTheme">@style/Theme.Micro.Dialog</item>
-        <item name="textViewStyle">@style/Widget.Micro.TextView</item>
-        <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item>
-        <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item>
-        <item name="windowBackground">@color/black</item>
-        <item name="windowContentOverlay">@null</item>
-        <item name="windowIsFloating">false</item>
-        <!-- Required to force windowInsets dispatch through application UI. -->
-        <item name="windowOverscan">true</item>
-    </style>
-
-    <style name="Theme.Micro" parent="Theme.MicroBase">
-    </style>
-
-    <style name="Theme.Micro.LightBase" parent="Theme.Material.Light.NoActionBar">
-        <item name="alertDialogTheme">@style/Theme.Micro.Dialog.Alert</item>
-        <item name="alertDialogStyle">@style/AlertDialog.Micro</item>
-        <item name="dialogTheme">@style/Theme.Micro.Dialog</item>
-        <item name="textViewStyle">@style/Widget.Micro.TextView</item>
-        <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item>
-        <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item>
-        <item name="windowBackground">@color/white</item>
-        <item name="windowContentOverlay">@null</item>
-        <item name="windowIsFloating">false</item>
-        <!-- Required to force windowInsets dispatch through application UI. -->
-        <item name="windowOverscan">true</item>
-    </style>
-
-    <!-- Indirection needed for overlays to make sure there is a common base parent -->
-    <style name="Theme.Micro.Light" parent="Theme.Micro.LightBase">
-    </style>
-
-    <style name="Theme.Micro.DialogBase" parent="Theme.Material.Light.Dialog">
-        <item name="windowTitleStyle">@android:style/DialogWindowTitle.Micro</item>
-        <item name="windowIsFloating">false</item>
-        <item name="windowFullscreen">true</item>
-        <item name="textAppearance">@style/TextAppearance.Micro</item>
-        <item name="textAppearanceInverse">@style/TextAppearance.Micro</item>
-        <!-- Required to force windowInsets dispatch through application UI. -->
-        <item name="windowOverscan">true</item>
-    </style>
-
-    <!-- Indirection needed for overlays to make sure there is a common base parent -->
-    <style name="Theme.Micro.Dialog" parent="Theme.Micro.DialogBase">
-    </style>
-
-    <style name="Theme.Micro.Dialog.Alert">
-        <item name="windowTitleStyle">@style/DialogWindowTitle.Micro</item>
-        <item name="alertDialogStyle">@style/AlertDialog.Micro</item>
-        <item name="windowIsFloating">false</item>
-        <item name="windowBackground">@android:color/transparent</item>
-        <item name="windowOverscan">true</item>
-        <item name="windowContentOverlay">@null</item>
-        <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
-        <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
-    </style>
-
-    <style name="Theme.Micro.Dialog.AppError" parent="Theme.Micro.Dialog">
-        <item name="windowBackground">@null</item>
-        <item name="alertDialogStyle">@style/AlertDialog.Micro</item>
-        <item name="windowOverscan">true</item>
-        <item name="windowCloseOnTouchOutside">false</item>
-        <item name="textSize">20sp</item>
-        <item name="fontFamily">sans-serif-condensed-light</item>
-        <item name="textColor">@color/micro_text_light</item>
-    </style>
-
-   <style name="Theme.Micro.Panel" parent="Theme.Material.Panel"  />
-   <style name="Theme.Micro.Light.Panel" parent="Theme.Material.Light.Panel"  />
-
-    <!-- Default theme for material style input methods, which is used by the
-         {@link android.inputmethodservice.InputMethodService} class.
-         This inherits from Theme.Panel, but sets up IME appropriate animations
-         and a few custom attributes. -->
-    <style name="Theme.Micro.InputMethod" parent="Theme.Micro.Panel">
-        <item name="windowAnimationStyle">@style/Animation.InputMethod</item>
-        <item name="imeFullscreenBackground">#1e282c</item>
-        <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
-        <item name="imeExtractExitAnimation">@anim/input_method_extract_exit</item>
-    </style>
-</resources>
diff --git a/core/res/res/xml-watch/default_zen_mode_config.xml b/core/res/res/xml-watch/default_zen_mode_config.xml
new file mode 100644
index 0000000..26af10c
--- /dev/null
+++ b/core/res/res/xml-watch/default_zen_mode_config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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.
+-->
+
+<!-- Default configuration for zen mode.  See android.service.notification.ZenModeConfig. -->
+<zen version="2">
+    <!-- Allow starred contacts to go through only. Repeated calls on.
+         Calls, messages, reminders, events off.-->
+    <allow from="2" repeatCallers="true" calls="false" messages="false" reminders="false"
+           events="false"/>
+</zen>
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
index 755e7c4..31ce95e 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
@@ -35,6 +35,7 @@
     /** The amount of time to sleep between issuing start/stop SCO in ms. */
     private static final long SCO_SLEEP_TIME = 2 * 1000;
 
+    private BluetoothAdapter mAdapter;
     private BluetoothTestUtils mTestUtils;
 
     @Override
@@ -42,13 +43,18 @@
         super.setUp();
 
         Context context = getInstrumentation().getTargetContext();
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
         mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE);
+
+        // Start all tests in a disabled state.
+        if (mAdapter.isEnabled()) {
+            mTestUtils.disable(mAdapter);
+        }
     }
 
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
-
         mTestUtils.close();
     }
 
@@ -61,13 +67,10 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        mTestUtils.disable(adapter);
-
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("enable iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.enable(adapter);
-            mTestUtils.disable(adapter);
+            mTestUtils.enable(mAdapter);
+            mTestUtils.disable(mAdapter);
         }
     }
 
@@ -80,18 +83,14 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.undiscoverable(adapter);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.undiscoverable(mAdapter);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("discoverable iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.discoverable(adapter);
-            mTestUtils.undiscoverable(adapter);
+            mTestUtils.discoverable(mAdapter);
+            mTestUtils.undiscoverable(mAdapter);
         }
-
-        mTestUtils.disable(adapter);
     }
 
     /**
@@ -103,18 +102,14 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.stopScan(adapter);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.stopScan(mAdapter);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("scan iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.startScan(adapter);
-            mTestUtils.stopScan(adapter);
+            mTestUtils.startScan(mAdapter);
+            mTestUtils.stopScan(mAdapter);
         }
-
-        mTestUtils.disable(adapter);
     }
 
     /**
@@ -125,19 +120,16 @@
         if (iterations == 0) {
             return;
         }
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.disablePan(adapter);
+
+        mTestUtils.enable(mAdapter);
+        mTestUtils.disablePan(mAdapter);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("testEnablePan iteration " + (i + 1) + " of "
                     + iterations);
-            mTestUtils.enablePan(adapter);
-            mTestUtils.disablePan(adapter);
+            mTestUtils.enablePan(mAdapter);
+            mTestUtils.disablePan(mAdapter);
         }
-
-        mTestUtils.disable(adapter);
     }
 
     /**
@@ -152,19 +144,16 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.unpair(adapter, device);
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("pair iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+            mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                     BluetoothTestRunner.sDevicePairPin);
-            mTestUtils.unpair(adapter, device);
+            mTestUtils.unpair(mAdapter, device);
         }
-        mTestUtils.disable(adapter);
     }
 
     /**
@@ -178,19 +167,16 @@
         if (iterations == 0) {
             return;
         }
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.unpair(adapter, device);
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("acceptPair iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+            mTestUtils.acceptPair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                     BluetoothTestRunner.sDevicePairPin);
-            mTestUtils.unpair(adapter, device);
+            mTestUtils.unpair(mAdapter, device);
         }
-        mTestUtils.disable(adapter);
     }
 
     /**
@@ -205,25 +191,22 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
+        mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                 BluetoothTestRunner.sDevicePairPin);
-        mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP, null);
+        mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.A2DP, null);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.connectProfile(adapter, device, BluetoothProfile.A2DP,
+            mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.A2DP,
                     String.format("connectA2dp(device=%s)", device));
-            mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP,
+            mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.A2DP,
                     String.format("disconnectA2dp(device=%s)", device));
         }
 
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.disable(adapter);
+        mTestUtils.unpair(mAdapter, device);
     }
 
     /**
@@ -238,25 +221,22 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
+        mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                 BluetoothTestRunner.sDevicePairPin);
-        mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null);
+        mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET,
+            mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HEADSET,
                     String.format("connectHeadset(device=%s)", device));
-            mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET,
+            mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET,
                     String.format("disconnectHeadset(device=%s)", device));
         }
 
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.disable(adapter);
+        mTestUtils.unpair(mAdapter, device);
     }
 
     /**
@@ -271,25 +251,22 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
+        mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                 BluetoothTestRunner.sDevicePairPin);
-        mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE, null);
+        mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE, null);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.connectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE,
+            mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE,
                     String.format("connectInput(device=%s)", device));
-            mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE,
+            mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE,
                     String.format("disconnectInput(device=%s)", device));
         }
 
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.disable(adapter);
+        mTestUtils.unpair(mAdapter, device);
     }
 
     /**
@@ -304,22 +281,19 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
+        mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                 BluetoothTestRunner.sDevicePairPin);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("connectPan iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.connectPan(adapter, device);
-            mTestUtils.disconnectPan(adapter, device);
+            mTestUtils.connectPan(mAdapter, device);
+            mTestUtils.disconnectPan(mAdapter, device);
         }
 
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.disable(adapter);
+        mTestUtils.unpair(mAdapter, device);
     }
 
     /**
@@ -334,26 +308,23 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.disablePan(adapter);
-        mTestUtils.enablePan(adapter);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.disablePan(mAdapter);
+        mTestUtils.enablePan(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
+        mTestUtils.acceptPair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                 BluetoothTestRunner.sDevicePairPin);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("incomingPanConnection iteration " + (i + 1) + " of "
                     + iterations);
-            mTestUtils.incomingPanConnection(adapter, device);
-            mTestUtils.incomingPanDisconnection(adapter, device);
+            mTestUtils.incomingPanConnection(mAdapter, device);
+            mTestUtils.incomingPanDisconnection(mAdapter, device);
         }
 
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.disablePan(adapter);
-        mTestUtils.disable(adapter);
+        mTestUtils.unpair(mAdapter, device);
+        mTestUtils.disablePan(mAdapter);
     }
 
     /**
@@ -368,28 +339,25 @@
             return;
         }
 
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
-        mTestUtils.disable(adapter);
-        mTestUtils.enable(adapter);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey,
+        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+        mTestUtils.enable(mAdapter);
+        mTestUtils.unpair(mAdapter, device);
+        mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                 BluetoothTestRunner.sDevicePairPin);
-        mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null);
-        mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET, null);
-        mTestUtils.stopSco(adapter, device);
+        mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null);
+        mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HEADSET, null);
+        mTestUtils.stopSco(mAdapter, device);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("startStopSco iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.startSco(adapter, device);
+            mTestUtils.startSco(mAdapter, device);
             sleep(SCO_SLEEP_TIME);
-            mTestUtils.stopSco(adapter, device);
+            mTestUtils.stopSco(mAdapter, device);
             sleep(SCO_SLEEP_TIME);
         }
 
-        mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.disable(adapter);
+        mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null);
+        mTestUtils.unpair(mAdapter, device);
     }
 
     private void sleep(long time) {
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index 0d9980a..ee15978 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -35,6 +35,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 public class BluetoothTestUtils extends Assert {
 
@@ -423,57 +425,40 @@
      * @param adapter The BT adapter.
      */
     public void enable(BluetoothAdapter adapter) {
-        int mask = (BluetoothReceiver.STATE_TURNING_ON_FLAG | BluetoothReceiver.STATE_ON_FLAG
-                | BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG);
-        long start = -1;
-        BluetoothReceiver receiver = getBluetoothReceiver(mask);
-
-        int state = adapter.getState();
-        switch (state) {
-            case BluetoothAdapter.STATE_ON:
-                assertTrue(adapter.isEnabled());
-                removeReceiver(receiver);
-                return;
-            case BluetoothAdapter.STATE_TURNING_ON:
-                assertFalse(adapter.isEnabled());
-                mask = 0; // Don't check for received intents since we might have missed them.
-                break;
-            case BluetoothAdapter.STATE_OFF:
-                assertFalse(adapter.isEnabled());
-                start = System.currentTimeMillis();
-                assertTrue(adapter.enable());
-                break;
-            case BluetoothAdapter.STATE_TURNING_OFF:
-                start = System.currentTimeMillis();
-                assertTrue(adapter.enable());
-                break;
-            default:
-                removeReceiver(receiver);
-                fail(String.format("enable() invalid state: state=%d", state));
-        }
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
-            state = adapter.getState();
-            if (state == BluetoothAdapter.STATE_ON
-                    && (receiver.getFiredFlags() & mask) == mask) {
-                assertTrue(adapter.isEnabled());
-                long finish = receiver.getCompletedTime();
-                if (start != -1 && finish != -1) {
-                    writeOutput(String.format("enable() completed in %d ms", (finish - start)));
-                } else {
-                    writeOutput("enable() completed");
+        writeOutput("Enabling Bluetooth adapter.");
+        assertFalse(adapter.isEnabled());
+        int btState = adapter.getState();
+        final Semaphore completionSemaphore = new Semaphore(0);
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+                    return;
                 }
-                removeReceiver(receiver);
-                return;
+                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                        BluetoothAdapter.ERROR);
+                if (state == BluetoothAdapter.STATE_ON) {
+                    completionSemaphore.release();
+                }
             }
-            sleep(POLL_TIME);
-        }
+        };
 
-        int firedFlags = receiver.getFiredFlags();
-        removeReceiver(receiver);
-        fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
-                state, BluetoothAdapter.STATE_ON, firedFlags, mask));
+        final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        mContext.registerReceiver(receiver, filter);
+        assertTrue(adapter.enable());
+        boolean success = false;
+        try {
+            success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
+            writeOutput(String.format("enable() completed in 0 ms"));
+        } catch (final InterruptedException e) {
+            // This should never happen but just in case it does, the test will fail anyway.
+        }
+        mContext.unregisterReceiver(receiver);
+        if (!success) {
+            fail(String.format("enable() timeout: state=%d (expected %d)", btState,
+                    BluetoothAdapter.STATE_ON));
+        }
     }
 
     /**
@@ -483,57 +468,40 @@
      * @param adapter The BT adapter.
      */
     public void disable(BluetoothAdapter adapter) {
-        int mask = (BluetoothReceiver.STATE_TURNING_OFF_FLAG | BluetoothReceiver.STATE_OFF_FLAG
-                | BluetoothReceiver.SCAN_MODE_NONE_FLAG);
-        long start = -1;
-        BluetoothReceiver receiver = getBluetoothReceiver(mask);
-
-        int state = adapter.getState();
-        switch (state) {
-            case BluetoothAdapter.STATE_OFF:
-                assertFalse(adapter.isEnabled());
-                removeReceiver(receiver);
-                return;
-            case BluetoothAdapter.STATE_TURNING_ON:
-                assertFalse(adapter.isEnabled());
-                start = System.currentTimeMillis();
-                break;
-            case BluetoothAdapter.STATE_ON:
-                assertTrue(adapter.isEnabled());
-                start = System.currentTimeMillis();
-                assertTrue(adapter.disable());
-                break;
-            case BluetoothAdapter.STATE_TURNING_OFF:
-                assertFalse(adapter.isEnabled());
-                mask = 0; // Don't check for received intents since we might have missed them.
-                break;
-            default:
-                removeReceiver(receiver);
-                fail(String.format("disable() invalid state: state=%d", state));
-        }
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
-            state = adapter.getState();
-            if (state == BluetoothAdapter.STATE_OFF
-                    && (receiver.getFiredFlags() & mask) == mask) {
-                assertFalse(adapter.isEnabled());
-                long finish = receiver.getCompletedTime();
-                if (start != -1 && finish != -1) {
-                    writeOutput(String.format("disable() completed in %d ms", (finish - start)));
-                } else {
-                    writeOutput("disable() completed");
+        writeOutput("Disabling Bluetooth adapter.");
+        assertTrue(adapter.isEnabled());
+        int btState = adapter.getState();
+        final Semaphore completionSemaphore = new Semaphore(0);
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+                    return;
                 }
-                removeReceiver(receiver);
-                return;
+                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                        BluetoothAdapter.ERROR);
+                if (state == BluetoothAdapter.STATE_OFF) {
+                    completionSemaphore.release();
+                }
             }
-            sleep(POLL_TIME);
-        }
+        };
 
-        int firedFlags = receiver.getFiredFlags();
-        removeReceiver(receiver);
-        fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
-                state, BluetoothAdapter.STATE_OFF, firedFlags, mask));
+        final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        mContext.registerReceiver(receiver, filter);
+        assertTrue(adapter.disable());
+        boolean success = false;
+        try {
+            success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
+            writeOutput(String.format("disable() completed in 0 ms"));
+        } catch (final InterruptedException e) {
+            // This should never happen but just in case it does, the test will fail anyway.
+        }
+        mContext.unregisterReceiver(receiver);
+        if (!success) {
+            fail(String.format("disable() timeout: state=%d (expected %d)", btState,
+                    BluetoothAdapter.STATE_OFF));
+        }
     }
 
     /**
@@ -543,40 +511,47 @@
      * @param adapter The BT adapter.
      */
     public void discoverable(BluetoothAdapter adapter) {
-        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
-
         if (!adapter.isEnabled()) {
             fail("discoverable() bluetooth not enabled");
         }
 
         int scanMode = adapter.getScanMode();
-        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+        if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
             return;
         }
 
-        BluetoothReceiver receiver = getBluetoothReceiver(mask);
-
-        assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, scanMode);
-        long start = System.currentTimeMillis();
-        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
-
-        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
-            scanMode = adapter.getScanMode();
-            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
-                    && (receiver.getFiredFlags() & mask) == mask) {
-                writeOutput(String.format("discoverable() completed in %d ms",
-                        (receiver.getCompletedTime() - start)));
-                removeReceiver(receiver);
-                return;
+        final Semaphore completionSemaphore = new Semaphore(0);
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (!BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) {
+                    return;
+                }
+                final int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
+                        BluetoothAdapter.SCAN_MODE_NONE);
+                if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+                    completionSemaphore.release();
+                }
             }
-            sleep(POLL_TIME);
-        }
+        };
 
-        int firedFlags = receiver.getFiredFlags();
-        removeReceiver(receiver);
-        fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
-                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
-                firedFlags, mask));
+        final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
+        mContext.registerReceiver(receiver, filter);
+        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
+        boolean success = false;
+        try {
+            success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT,
+                    TimeUnit.MILLISECONDS);
+            writeOutput(String.format("discoverable() completed in 0 ms"));
+        } catch (final InterruptedException e) {
+            // This should never happen but just in case it does, the test will fail anyway.
+        }
+        mContext.unregisterReceiver(receiver);
+        if (!success) {
+            fail(String.format("discoverable() timeout: scanMode=%d (expected %d)", scanMode,
+                    BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
+        }
     }
 
     /**
@@ -586,40 +561,47 @@
      * @param adapter The BT adapter.
      */
     public void undiscoverable(BluetoothAdapter adapter) {
-        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG;
-
         if (!adapter.isEnabled()) {
             fail("undiscoverable() bluetooth not enabled");
         }
 
         int scanMode = adapter.getScanMode();
-        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
+        if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
             return;
         }
 
-        BluetoothReceiver receiver = getBluetoothReceiver(mask);
-
-        assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, scanMode);
-        long start = System.currentTimeMillis();
-        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
-
-        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
-            scanMode = adapter.getScanMode();
-            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
-                    && (receiver.getFiredFlags() & mask) == mask) {
-                writeOutput(String.format("undiscoverable() completed in %d ms",
-                        (receiver.getCompletedTime() - start)));
-                removeReceiver(receiver);
-                return;
+        final Semaphore completionSemaphore = new Semaphore(0);
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (!BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) {
+                    return;
+                }
+                final int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
+                        BluetoothAdapter.SCAN_MODE_NONE);
+                if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
+                    completionSemaphore.release();
+                }
             }
-            sleep(POLL_TIME);
-        }
+        };
 
-        int firedFlags = receiver.getFiredFlags();
-        removeReceiver(receiver);
-        fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
-                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags,
-                mask));
+        final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
+        mContext.registerReceiver(receiver, filter);
+        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
+        boolean success = false;
+        try {
+            success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT,
+                    TimeUnit.MILLISECONDS);
+            writeOutput(String.format("undiscoverable() completed in 0 ms"));
+        } catch (InterruptedException e) {
+            // This should never happen but just in case it does, the test will fail anyway.
+        }
+        mContext.unregisterReceiver(receiver);
+        if (!success) {
+            fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d)", scanMode,
+                    BluetoothAdapter.SCAN_MODE_CONNECTABLE));
+        }
     }
 
     /**
diff --git a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
index da8bc1d..7935880 100644
--- a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
@@ -45,6 +45,7 @@
     private static final int TEST_CMD = 18;
     private static final int TEST_ARG1 = 33;
     private static final int TEST_ARG2 = 182;
+    private static final Object TEST_OBJ = "hello";
 
     @Mock AlarmManager mAlarmManager;
     WakeupMessage mMessage;
@@ -92,7 +93,7 @@
                 mListenerCaptor.capture(), any(Handler.class));
 
         mMessage = new WakeupMessage(context, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
-                TEST_ARG2);
+                TEST_ARG2, TEST_OBJ);
     }
 
     /**
@@ -114,6 +115,7 @@
         assertEquals("what", TEST_CMD, mHandler.getLastMessage().what);
         assertEquals("arg1", TEST_ARG1, mHandler.getLastMessage().arg1);
         assertEquals("arg2", TEST_ARG2, mHandler.getLastMessage().arg2);
+        assertEquals("obj", TEST_OBJ, mHandler.getLastMessage().obj);
     }
 
     /**
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 6762bea..6bb93aec 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -46,6 +46,7 @@
 import android.util.Log;
 import android.util.LongArray;
 import android.util.PathParser;
+import android.util.Property;
 import android.util.TimeUtils;
 import android.view.Choreographer;
 import android.view.DisplayListCanvas;
@@ -157,7 +158,7 @@
     private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
 
     /** Local, mutable animator set. */
-    private VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimatorUI(this);
+    private VectorDrawableAnimator mAnimatorSet;
 
     /**
      * The resources against which this drawable was created. Used to attempt
@@ -182,6 +183,7 @@
 
     private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
         mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
+        mAnimatorSet = new VectorDrawableAnimatorRT(this);
         mRes = res;
     }
 
@@ -390,9 +392,12 @@
                             R.styleable.AnimatedVectorDrawableTarget_animation, 0);
                     if (animResId != 0) {
                         if (theme != null) {
-                            final Animator objectAnimator = AnimatorInflater.loadAnimator(
+                            // The animator here could be ObjectAnimator or AnimatorSet.
+                            final Animator animator = AnimatorInflater.loadAnimator(
                                     res, theme, animResId, pathErrorScale);
-                            state.addTargetAnimator(target, objectAnimator);
+                            updateAnimatorProperty(animator, target, state.mVectorDrawable,
+                                    state.mShouldIgnoreInvalidAnim);
+                            state.addTargetAnimator(target, animator);
                         } else {
                             // The animation may be theme-dependent. As a
                             // workaround until Animator has full support for
@@ -414,6 +419,55 @@
         mRes = state.mPendingAnims == null ? null : res;
     }
 
+    private static void updateAnimatorProperty(Animator animator, String targetName,
+            VectorDrawable vectorDrawable, boolean ignoreInvalidAnim) {
+        if (animator instanceof ObjectAnimator) {
+            // Change the property of the Animator from using reflection based on the property
+            // name to a Property object that wraps the setter and getter for modifying that
+            // specific property for a given object. By replacing the reflection with a direct call,
+            // we can largely reduce the time it takes for a animator to modify a VD property.
+            PropertyValuesHolder[] holders = ((ObjectAnimator) animator).getValues();
+            for (int i = 0; i < holders.length; i++) {
+                PropertyValuesHolder pvh = holders[i];
+                String propertyName = pvh.getPropertyName();
+                Object targetNameObj = vectorDrawable.getTargetByName(targetName);
+                Property property = null;
+                if (targetNameObj instanceof VectorDrawable.VObject) {
+                    property = ((VectorDrawable.VObject) targetNameObj).getProperty(propertyName);
+                } else if (targetNameObj instanceof VectorDrawable.VectorDrawableState) {
+                    property = ((VectorDrawable.VectorDrawableState) targetNameObj)
+                            .getProperty(propertyName);
+                }
+                if (property != null) {
+                    if (containsSameValueType(pvh, property)) {
+                        pvh.setProperty(property);
+                    } else if (!ignoreInvalidAnim) {
+                        throw new RuntimeException("Wrong valueType for Property: " + propertyName
+                                + ".  Expected type: " + property.getType().toString() + ". Actual "
+                                + "type defined in resources: " + pvh.getValueType().toString());
+
+                    }
+                }
+            }
+        } else if (animator instanceof AnimatorSet) {
+            for (Animator anim : ((AnimatorSet) animator).getChildAnimations()) {
+                updateAnimatorProperty(anim, targetName, vectorDrawable, ignoreInvalidAnim);
+            }
+        }
+    }
+
+    private static boolean containsSameValueType(PropertyValuesHolder holder, Property property) {
+        Class type1 = holder.getValueType();
+        Class type2 = property.getType();
+        if (type1 == float.class || type1 == Float.class) {
+            return type2 == float.class || type2 == Float.class;
+        } else if (type1 == int.class || type1 == Integer.class) {
+            return type2 == int.class || type2 == Integer.class;
+        } else {
+            return type1 == type2;
+        }
+    }
+
     /**
      * Force to animate on UI thread.
      * @hide
@@ -462,6 +516,8 @@
         @Config int mChangingConfigurations;
         VectorDrawable mVectorDrawable;
 
+        private final boolean mShouldIgnoreInvalidAnim;
+
         /** Animators that require a theme before inflation. */
         ArrayList<PendingAnimator> mPendingAnims;
 
@@ -473,6 +529,7 @@
 
         public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
                 Callback owner, Resources res) {
+            mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation();
             if (copy != null) {
                 mChangingConfigurations = copy.mChangingConfigurations;
 
@@ -616,8 +673,10 @@
 
                 for (int i = 0, count = pendingAnims.size(); i < count; i++) {
                     final PendingAnimator pendingAnimator = pendingAnims.get(i);
-                    final Animator objectAnimator = pendingAnimator.newInstance(res, t);
-                    addTargetAnimator(pendingAnimator.target, objectAnimator);
+                    final Animator animator = pendingAnimator.newInstance(res, t);
+                    updateAnimatorProperty(animator, pendingAnimator.target, mVectorDrawable,
+                            mShouldIgnoreInvalidAnim);
+                    addTargetAnimator(pendingAnimator.target, animator);
                 }
             }
         }
@@ -982,6 +1041,9 @@
         private static final int REVERSE_ANIMATION = 2;
         private static final int RESET_ANIMATION = 3;
         private static final int END_ANIMATION = 4;
+
+        // If the duration of an animation is more than 300 frames, we cap the sample size to 300.
+        private static final int MAX_SAMPLE_POINTS = 300;
         private AnimatorListener mListener = null;
         private final LongArray mStartDelays = new LongArray();
         private PropertyValuesHolder.PropertyValues mTmpValues =
@@ -992,14 +1054,12 @@
         private boolean mInitialized = false;
         private boolean mIsReversible = false;
         private boolean mIsInfinite = false;
-        // This needs to be set before parsing starts.
-        private boolean mShouldIgnoreInvalidAnim;
         // TODO: Consider using NativeAllocationRegistery to track native allocation
         private final VirtualRefBasePtr mSetRefBasePtr;
         private WeakReference<RenderNode> mLastSeenTarget = null;
         private int mLastListenerId = 0;
         private final IntArray mPendingAnimationActions = new IntArray();
-        private final Drawable mDrawable;
+        private final AnimatedVectorDrawable mDrawable;
 
         VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
             mDrawable = drawable;
@@ -1016,8 +1076,10 @@
                 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
                         "re-initialized");
             }
-            mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation();
             parseAnimatorSet(set, 0);
+            long vectorDrawableTreePtr = mDrawable.mAnimatedVectorState.mVectorDrawable
+                    .getNativeTree();
+            nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr);
             mInitialized = true;
             mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE;
 
@@ -1077,7 +1139,7 @@
                     }  else if (target instanceof VectorDrawable.VFullPath) {
                         createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
                                 startTime);
-                    } else if (!mShouldIgnoreInvalidAnim) {
+                    } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
                         throw new IllegalArgumentException("ClipPath only supports PathData " +
                                 "property");
                     }
@@ -1086,7 +1148,7 @@
             } else if (target instanceof VectorDrawable.VectorDrawableState) {
                 createRTAnimatorForRootGroup(values, animator,
                         (VectorDrawable.VectorDrawableState) target, startTime);
-            } else if (!mShouldIgnoreInvalidAnim) {
+            } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
                 // Should never get here
                 throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
                         "or ConstantState, " + target == null ? "Null target" : target.getClass() +
@@ -1121,8 +1183,8 @@
                 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
                         (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
                 if (mTmpValues.dataSource != null) {
-                    float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
-                            .getDuration());
+                    float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource,
+                            animator.getDuration());
                     nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
                 }
                 createNativeChildAnimator(propertyPtr, startTime, animator);
@@ -1149,7 +1211,7 @@
             long nativePtr = target.getNativePtr();
             if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
                 if (propertyId < 0) {
-                    if (mShouldIgnoreInvalidAnim) {
+                    if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
                         return;
                     } else {
                         throw new IllegalArgumentException("Property: " + mTmpValues.propertyName
@@ -1158,12 +1220,24 @@
                 }
                 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
                         (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
+                if (mTmpValues.dataSource != null) {
+                    // Pass keyframe data to native, if any.
+                    float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource,
+                            animator.getDuration());
+                    nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+                }
 
             } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
                 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
                         (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
+                if (mTmpValues.dataSource != null) {
+                    // Pass keyframe data to native, if any.
+                    int[] dataPoints = createIntDataPoints(mTmpValues.dataSource,
+                            animator.getDuration());
+                    nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+                }
             } else {
-                if (mShouldIgnoreInvalidAnim) {
+                if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
                     return;
                 } else {
                     throw new UnsupportedOperationException("Unsupported type: " +
@@ -1171,45 +1245,63 @@
                             "supported for Paths.");
                 }
             }
-            if (mTmpValues.dataSource != null) {
-                float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
-                        .getDuration());
-                nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
-            }
             createNativeChildAnimator(propertyPtr, startTime, animator);
         }
 
         private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
                 ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
                 long startTime) {
-                long nativePtr = target.getNativeRenderer();
-                if (!animator.getPropertyName().equals("alpha")) {
-                    if (mShouldIgnoreInvalidAnim) {
-                        return;
-                    } else {
-                        throw new UnsupportedOperationException("Only alpha is supported for root "
-                                + "group");
-                    }
+            long nativePtr = target.getNativeRenderer();
+            if (!animator.getPropertyName().equals("alpha")) {
+                if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
+                    return;
+                } else {
+                    throw new UnsupportedOperationException("Only alpha is supported for root "
+                            + "group");
                 }
-                Float startValue = null;
-                Float endValue = null;
-                for (int i = 0; i < values.length; i++) {
-                    values[i].getPropertyValues(mTmpValues);
-                    if (mTmpValues.propertyName.equals("alpha")) {
-                        startValue = (Float) mTmpValues.startValue;
-                        endValue = (Float) mTmpValues.endValue;
-                        break;
-                    }
+            }
+            Float startValue = null;
+            Float endValue = null;
+            for (int i = 0; i < values.length; i++) {
+                values[i].getPropertyValues(mTmpValues);
+                if (mTmpValues.propertyName.equals("alpha")) {
+                    startValue = (Float) mTmpValues.startValue;
+                    endValue = (Float) mTmpValues.endValue;
+                    break;
                 }
-                if (startValue == null && endValue == null) {
-                    if (mShouldIgnoreInvalidAnim) {
-                        return;
-                    } else {
-                        throw new UnsupportedOperationException("No alpha values are specified");
-                    }
+            }
+            if (startValue == null && endValue == null) {
+                if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
+                    return;
+                } else {
+                    throw new UnsupportedOperationException("No alpha values are specified");
                 }
-                long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
-                createNativeChildAnimator(propertyPtr, startTime, animator);
+            }
+            long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
+            if (mTmpValues.dataSource != null) {
+                // Pass keyframe data to native, if any.
+                float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource,
+                        animator.getDuration());
+                nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+            }
+            createNativeChildAnimator(propertyPtr, startTime, animator);
+        }
+
+        /**
+         * Calculate the amount of frames an animation will run based on duration.
+         */
+        private static int getFrameCount(long duration) {
+            long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
+            int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
+            int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
+            // We need 2 frames of data minimum.
+            numAnimFrames = Math.max(2, numAnimFrames);
+            if (numAnimFrames > MAX_SAMPLE_POINTS) {
+                Log.w("AnimatedVectorDrawable", "Duration for the animation is too long :" +
+                        duration + ", the animation will subsample the keyframe or path data.");
+                numAnimFrames = MAX_SAMPLE_POINTS;
+            }
+            return numAnimFrames;
         }
 
         // These are the data points that define the value of the animating properties.
@@ -1217,11 +1309,9 @@
         // a point on the path corresponds to the values of translateX and translateY.
         // TODO: (Optimization) We should pass the path down in native and chop it into segments
         // in native.
-        private static float[] createDataPoints(
+        private static float[] createFloatDataPoints(
                 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
-            long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
-            int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
-            int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
+            int numAnimFrames = getFrameCount(duration);
             float values[] = new float[numAnimFrames];
             float lastFrame = numAnimFrames - 1;
             for (int i = 0; i < numAnimFrames; i++) {
@@ -1231,6 +1321,18 @@
             return values;
         }
 
+        private static int[] createIntDataPoints(
+                PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
+            int numAnimFrames = getFrameCount(duration);
+            int values[] = new int[numAnimFrames];
+            float lastFrame = numAnimFrames - 1;
+            for (int i = 0; i < numAnimFrames; i++) {
+                float fraction = i / lastFrame;
+                values[i] = (Integer) dataSource.getValueAtFraction(fraction);
+            }
+            return values;
+        }
+
         private void createNativeChildAnimator(long propertyPtr, long extraDelay,
                                                ObjectAnimator animator) {
             long duration = animator.getDuration();
@@ -1254,16 +1356,19 @@
          * to the last seen RenderNode target and start right away.
          */
         protected void recordLastSeenTarget(DisplayListCanvas canvas) {
-            mLastSeenTarget = new WeakReference<RenderNode>(
-                    RenderNodeAnimatorSetHelper.getTarget(canvas));
-            if (mPendingAnimationActions.size() > 0 && useLastSeenTarget()) {
-                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                    Log.d(LOGTAG, "Target is set in the next frame");
+            final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas);
+            mLastSeenTarget = new WeakReference<RenderNode>(node);
+            // Add the animator to the list of animators on every draw
+            if (mInitialized || mPendingAnimationActions.size() > 0) {
+                if (useTarget(node)) {
+                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                        Log.d(LOGTAG, "Target is set in the next frame");
+                    }
+                    for (int i = 0; i < mPendingAnimationActions.size(); i++) {
+                        handlePendingAction(mPendingAnimationActions.get(i));
+                    }
+                    mPendingAnimationActions.clear();
                 }
-                for (int i = 0; i < mPendingAnimationActions.size(); i++) {
-                    handlePendingAction(mPendingAnimationActions.get(i));
-                }
-                mPendingAnimationActions.clear();
             }
         }
 
@@ -1285,10 +1390,15 @@
         private boolean useLastSeenTarget() {
             if (mLastSeenTarget != null) {
                 final RenderNode target = mLastSeenTarget.get();
-                if (target != null && target.isAttached()) {
-                    target.addAnimator(this);
-                    return true;
-                }
+                return useTarget(target);
+            }
+            return false;
+        }
+
+        private boolean useTarget(RenderNode target) {
+            if (target != null && target.isAttached()) {
+                target.registerVectorDrawableAnimator(this);
+                return true;
             }
             return false;
         }
@@ -1483,6 +1593,7 @@
     }
 
     private static native long nCreateAnimatorSet();
+    private static native void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr);
     private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
              long nativeInterpolator, long startDelay, long duration, int repeatCount);
 
@@ -1498,6 +1609,7 @@
     private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
             float endValue);
     private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
+    private static native void nSetPropertyHolderData(long nativePtr, int[] data, int length);
     private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
     private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
     private static native void nEnd(long animatorSetPtr);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index f5592fa..a8c8737 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -34,9 +34,12 @@
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.FloatProperty;
+import android.util.IntProperty;
 import android.util.LayoutDirection;
 import android.util.Log;
 import android.util.PathParser;
+import android.util.Property;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -758,6 +761,13 @@
         return mVectorState.mAutoMirrored;
     }
 
+    /**
+     * @hide
+     */
+    public long getNativeTree() {
+        return mVectorState.getNativeRenderer();
+    }
+
     static class VectorDrawableState extends ConstantState {
         // Variables below need to be copied (deep copy if applicable) for mutation.
         int[] mThemeAttrs;
@@ -790,6 +800,26 @@
         int mLastSWCachePixelCount = 0;
         int mLastHWCachePixelCount = 0;
 
+        final static Property<VectorDrawableState, Float> ALPHA =
+                new FloatProperty<VectorDrawableState>("alpha") {
+                    @Override
+                    public void setValue(VectorDrawableState state, float value) {
+                        state.setAlpha(value);
+                    }
+
+                    @Override
+                    public Float get(VectorDrawableState state) {
+                        return state.getAlpha();
+                    }
+                };
+
+        Property getProperty(String propertyName) {
+            if (ALPHA.getName().equals(propertyName)) {
+                return ALPHA;
+            }
+            return null;
+        }
+
         // This tracks the total native allocation for all the nodes.
         private int mAllocationOfAllNodes = 0;
 
@@ -967,7 +997,7 @@
     }
 
     static class VGroup extends VObject {
-        private static final int ROTATE_INDEX = 0;
+        private static final int ROTATION_INDEX = 0;
         private static final int PIVOT_X_INDEX = 1;
         private static final int PIVOT_Y_INDEX = 2;
         private static final int SCALE_X_INDEX = 3;
@@ -978,7 +1008,7 @@
 
         private static final int NATIVE_ALLOCATION_SIZE = 100;
 
-        private static final HashMap<String, Integer> sPropertyMap =
+        private static final HashMap<String, Integer> sPropertyIndexMap =
                 new HashMap<String, Integer>() {
                     {
                         put("translateX", TRANSLATE_X_INDEX);
@@ -987,19 +1017,123 @@
                         put("scaleY", SCALE_Y_INDEX);
                         put("pivotX", PIVOT_X_INDEX);
                         put("pivotY", PIVOT_Y_INDEX);
-                        put("rotation", ROTATE_INDEX);
+                        put("rotation", ROTATION_INDEX);
                     }
                 };
 
         static int getPropertyIndex(String propertyName) {
-            if (sPropertyMap.containsKey(propertyName)) {
-                return sPropertyMap.get(propertyName);
+            if (sPropertyIndexMap.containsKey(propertyName)) {
+                return sPropertyIndexMap.get(propertyName);
             } else {
                 // property not found
                 return -1;
             }
         }
 
+        // Below are the Properties that wrap the setters to avoid reflection overhead in animations
+        private static final Property<VGroup, Float> TRANSLATE_X =
+                new FloatProperty<VGroup> ("translateX") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setTranslateX(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getTranslateX();
+                    }
+                };
+
+        private static final Property<VGroup, Float> TRANSLATE_Y =
+                new FloatProperty<VGroup> ("translateY") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setTranslateY(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getTranslateY();
+                    }
+        };
+
+        private static final Property<VGroup, Float> SCALE_X =
+                new FloatProperty<VGroup> ("scaleX") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setScaleX(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getScaleX();
+                    }
+                };
+
+        private static final Property<VGroup, Float> SCALE_Y =
+                new FloatProperty<VGroup> ("scaleY") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setScaleY(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getScaleY();
+                    }
+                };
+
+        private static final Property<VGroup, Float> PIVOT_X =
+                new FloatProperty<VGroup> ("pivotX") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setPivotX(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getPivotX();
+                    }
+                };
+
+        private static final Property<VGroup, Float> PIVOT_Y =
+                new FloatProperty<VGroup> ("pivotY") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setPivotY(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getPivotY();
+                    }
+                };
+
+        private static final Property<VGroup, Float> ROTATION =
+                new FloatProperty<VGroup> ("rotation") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setRotation(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getRotation();
+                    }
+                };
+
+        private static final HashMap<String, Property> sPropertyMap =
+                new HashMap<String, Property>() {
+                    {
+                        put("translateX", TRANSLATE_X);
+                        put("translateY", TRANSLATE_Y);
+                        put("scaleX", SCALE_X);
+                        put("scaleY", SCALE_Y);
+                        put("pivotX", PIVOT_X);
+                        put("pivotY", PIVOT_Y);
+                        put("rotation", ROTATION);
+                    }
+                };
         // Temp array to store transform values obtained from native.
         private float[] mTransform;
         /////////////////////////////////////////////////////
@@ -1055,6 +1189,15 @@
             mNativePtr = nCreateGroup();
         }
 
+        Property getProperty(String propertyName) {
+            if (sPropertyMap.containsKey(propertyName)) {
+                return sPropertyMap.get(propertyName);
+            } else {
+                // property not found
+                return null;
+            }
+        }
+
         public String getGroupName() {
             return mGroupName;
         }
@@ -1102,7 +1245,7 @@
                 throw new RuntimeException("Error: inconsistent property count");
             }
             float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation,
-                    mTransform[ROTATE_INDEX]);
+                    mTransform[ROTATION_INDEX]);
             float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX,
                     mTransform[PIVOT_X_INDEX]);
             float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY,
@@ -1288,6 +1431,27 @@
         String mPathName;
         @Config int mChangingConfigurations;
 
+        private static final Property<VPath, PathParser.PathData> PATH_DATA =
+                new Property<VPath, PathParser.PathData>(PathParser.PathData.class, "pathData") {
+                    @Override
+                    public void set(VPath object, PathParser.PathData data) {
+                        object.setPathData(data);
+                    }
+
+                    @Override
+                    public PathParser.PathData get(VPath object) {
+                        return object.getPathData();
+                    }
+                };
+
+        Property getProperty(String propertyName) {
+            if (PATH_DATA.getName().equals(propertyName)) {
+                return PATH_DATA;
+            }
+            // property not found
+            return null;
+        }
+
         public VPath() {
             // Empty constructor.
         }
@@ -1410,7 +1574,7 @@
 
         private static final int NATIVE_ALLOCATION_SIZE = 264;
         // Property map for animatable attributes.
-        private final static HashMap<String, Integer> sPropertyMap
+        private final static HashMap<String, Integer> sPropertyIndexMap
                 = new HashMap<String, Integer> () {
             {
                 put("strokeWidth", STROKE_WIDTH_INDEX);
@@ -1424,6 +1588,125 @@
             }
         };
 
+        // Below are the Properties that wrap the setters to avoid reflection overhead in animations
+        private static final Property<VFullPath, Float> STROKE_WIDTH =
+                new FloatProperty<VFullPath> ("strokeWidth") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setStrokeWidth(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getStrokeWidth();
+                    }
+                };
+
+        private static final Property<VFullPath, Integer> STROKE_COLOR =
+                new IntProperty<VFullPath> ("strokeColor") {
+                    @Override
+                    public void setValue(VFullPath object, int value) {
+                        object.setStrokeColor(value);
+                    }
+
+                    @Override
+                    public Integer get(VFullPath object) {
+                        return object.getStrokeColor();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> STROKE_ALPHA =
+                new FloatProperty<VFullPath> ("strokeAlpha") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setStrokeAlpha(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getStrokeAlpha();
+                    }
+                };
+
+        private static final Property<VFullPath, Integer> FILL_COLOR =
+                new IntProperty<VFullPath>("fillColor") {
+                    @Override
+                    public void setValue(VFullPath object, int value) {
+                        object.setFillColor(value);
+                    }
+
+                    @Override
+                    public Integer get(VFullPath object) {
+                        return object.getFillColor();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> FILL_ALPHA =
+                new FloatProperty<VFullPath> ("fillAlpha") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setFillAlpha(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getFillAlpha();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> TRIM_PATH_START =
+                new FloatProperty<VFullPath> ("trimPathStart") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setTrimPathStart(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getTrimPathStart();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> TRIM_PATH_END =
+                new FloatProperty<VFullPath> ("trimPathEnd") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setTrimPathEnd(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getTrimPathEnd();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> TRIM_PATH_OFFSET =
+                new FloatProperty<VFullPath> ("trimPathOffset") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setTrimPathOffset(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getTrimPathOffset();
+                    }
+                };
+
+        private final static HashMap<String, Property> sPropertyMap
+                = new HashMap<String, Property> () {
+            {
+                put("strokeWidth", STROKE_WIDTH);
+                put("strokeColor", STROKE_COLOR);
+                put("strokeAlpha", STROKE_ALPHA);
+                put("fillColor", FILL_COLOR);
+                put("fillAlpha", FILL_ALPHA);
+                put("trimPathStart", TRIM_PATH_START);
+                put("trimPathEnd", TRIM_PATH_END);
+                put("trimPathOffset", TRIM_PATH_OFFSET);
+            }
+        };
+
         // Temp array to store property data obtained from native getter.
         private byte[] mPropertyData;
         /////////////////////////////////////////////////////
@@ -1446,11 +1729,24 @@
             mFillColors = copy.mFillColors;
         }
 
+        Property getProperty(String propertyName) {
+            Property p = super.getProperty(propertyName);
+            if (p != null) {
+                return p;
+            }
+            if (sPropertyMap.containsKey(propertyName)) {
+                return sPropertyMap.get(propertyName);
+            } else {
+                // property not found
+                return null;
+            }
+        }
+
         int getPropertyIndex(String propertyName) {
-            if (!sPropertyMap.containsKey(propertyName)) {
+            if (!sPropertyIndexMap.containsKey(propertyName)) {
                 return -1;
             } else {
-                return sPropertyMap.get(propertyName);
+                return sPropertyIndexMap.get(propertyName);
             }
         }
 
@@ -1784,6 +2080,7 @@
         abstract boolean onStateChange(int[] state);
         abstract boolean isStateful();
         abstract int getNativeSize();
+        abstract Property getProperty(String propertyName);
     }
 
     private static native long nCreateTree(long rootGroupPtr);
diff --git a/libs/hwui/AnimationContext.h b/libs/hwui/AnimationContext.h
index 909ed36..801fd87 100644
--- a/libs/hwui/AnimationContext.h
+++ b/libs/hwui/AnimationContext.h
@@ -100,6 +100,8 @@
 
     ANDROID_API virtual void destroy();
 
+    ANDROID_API virtual void detachAnimators() {}
+
 private:
     friend class AnimationHandle;
     void addAnimationHandle(AnimationHandle* handle);
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 4d65782..dc18018 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -274,6 +274,10 @@
     return playTime >= mDuration;
 }
 
+nsecs_t BaseRenderNodeAnimator::getRemainingPlayTime() {
+    return mPlayState == PlayState::Reversing ? mPlayTime : mDuration - mPlayTime;
+}
+
 void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) {
     if (mPlayState < PlayState::Finished) {
         mPlayState = PlayState::Finished;
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index fdae0f3..9476750 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -62,19 +62,23 @@
     }
     bool mayRunAsync() { return mMayRunAsync; }
     ANDROID_API void start();
-    ANDROID_API void reset();
+    ANDROID_API virtual void reset();
     ANDROID_API void reverse();
     // Terminates the animation at its current progress.
     ANDROID_API void cancel();
 
     // Terminates the animation and skip to the end of the animation.
-    ANDROID_API void end();
+    ANDROID_API virtual void end();
 
     void attach(RenderNode* target);
     virtual void onAttached() {}
     void detach() { mTarget = nullptr; }
-    void pushStaging(AnimationContext& context);
-    bool animate(AnimationContext& context);
+    ANDROID_API void pushStaging(AnimationContext& context);
+    ANDROID_API bool animate(AnimationContext& context);
+
+    // Returns the remaining time in ms for the animation. Note this should only be called during
+    // an animation on RenderThread.
+    ANDROID_API nsecs_t getRemainingPlayTime();
 
     bool isRunning() { return mPlayState == PlayState::Running
             || mPlayState == PlayState::Reversing; }
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index b572bda..28be05c 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -45,7 +45,7 @@
         , regions(stdAllocator)
         , referenceHolders(stdAllocator)
         , functors(stdAllocator)
-        , pushStagingFunctors(stdAllocator)
+        , vectorDrawables(stdAllocator)
         , hasDrawOps(false) {
 }
 
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 5b3227b..ccf71c6 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -69,6 +69,11 @@
 typedef DrawRenderNodeOp NodeOpType;
 #endif
 
+namespace VectorDrawable {
+class Tree;
+};
+typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
+
 /**
  * Holds data used in the playback a tree of DisplayLists.
  */
@@ -110,16 +115,6 @@
     LinearAllocator mReplayAllocator;
 };
 
-/**
- * Functor that can be used for objects with data in both UI thread and RT to keep the data
- * in sync. This functor, when added to DisplayList, will be call during DisplayList sync.
- */
-struct PushStagingFunctor {
-    PushStagingFunctor() {}
-    virtual ~PushStagingFunctor() {}
-    virtual void operator ()() {}
-};
-
 struct FunctorContainer {
     Functor* functor;
     GlFunctorLifecycleListener* listener;
@@ -161,7 +156,7 @@
 
     const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; }
     const LsaVector<FunctorContainer>& getFunctors() const { return functors; }
-    const LsaVector<PushStagingFunctor*>& getPushStagingFunctors() { return pushStagingFunctors; }
+    const LsaVector<VectorDrawableRoot*>& getVectorDrawables() { return vectorDrawables; }
 
     size_t addChild(NodeOpType* childOp);
 
@@ -203,10 +198,10 @@
     // List of functors
     LsaVector<FunctorContainer> functors;
 
-    // List of functors that need to be notified of pushStaging. Note that this list gets nothing
+    // List of VectorDrawables that need to be notified of pushStaging. Note that this list gets nothing
     // but a callback during sync DisplayList, unlike the list of functors defined above, which
     // gets special treatment exclusive for webview.
-    LsaVector<PushStagingFunctor*> pushStagingFunctors;
+    LsaVector<VectorDrawableRoot*> vectorDrawables;
 
     bool hasDrawOps; // only used if !HWUI_NEW_OPS
 
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index ca968ce..bec66295 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -417,7 +417,7 @@
 
 void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
     mDisplayList->ref(tree);
-    mDisplayList->pushStagingFunctors.push_back(tree->getFunctor());
+    mDisplayList->vectorDrawables.push_back(tree);
     addDrawOp(new (alloc()) DrawVectorDrawableOp(tree, tree->stagingProperties()->getBounds()));
 }
 
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
index b29f91f..e416e0c 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.cpp
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -30,6 +30,11 @@
             interpolator, startDelay, duration, repeatCount);
     mAnimators.emplace_back(animator);
     setListener(new PropertyAnimatorSetListener(this));
+
+    // Check whether any child animator is infinite after adding it them to the set.
+    if (repeatCount == -1) {
+        mIsInfinite = true;
+    }
 }
 
 PropertyValuesAnimatorSet::PropertyValuesAnimatorSet()
@@ -78,15 +83,27 @@
 void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
     init();
     mOneShotListener = listener;
+    mRequestId++;
     BaseRenderNodeAnimator::start();
 }
 
 void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
     init();
     mOneShotListener = listener;
+    mRequestId++;
     BaseRenderNodeAnimator::reverse();
 }
 
+void PropertyValuesAnimatorSet::reset() {
+    mRequestId++;
+    BaseRenderNodeAnimator::reset();
+}
+
+void PropertyValuesAnimatorSet::end() {
+    mRequestId++;
+    BaseRenderNodeAnimator::end();
+}
+
 void PropertyValuesAnimatorSet::init() {
     if (mInitialized) {
         return;
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
index c7ae7c0..49021bc 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.h
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -43,6 +43,7 @@
     float mLatestFraction = 0.0f;
 };
 
+// TODO: This class should really be named VectorDrawableAnimator
 class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator {
 public:
     friend class PropertyAnimatorSetListener;
@@ -50,11 +51,19 @@
 
     void start(AnimationListener* listener);
     void reverse(AnimationListener* listener);
+    virtual void reset() override;
+    virtual void end() override;
 
     void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
             Interpolator* interpolators, int64_t startDelays,
             nsecs_t durations, int repeatCount);
     virtual uint32_t dirtyMask();
+    bool isInfinite() { return mIsInfinite; }
+    void setVectorDrawable(VectorDrawableRoot* vd) { mVectorDrawable = vd; }
+    VectorDrawableRoot* getVectorDrawable() const { return mVectorDrawable; }
+    AnimationListener* getOneShotListener() { return mOneShotListener.get(); }
+    void clearOneShotListener() { mOneShotListener = nullptr; }
+    uint32_t getRequestId() const { return mRequestId; }
 
 protected:
     virtual float getValue(RenderNode* target) const override;
@@ -69,6 +78,11 @@
     std::vector< std::unique_ptr<PropertyAnimator> > mAnimators;
     float mLastFraction = 0.0f;
     bool mInitialized = false;
+    VectorDrawableRoot* mVectorDrawable = nullptr;
+    bool mIsInfinite = false;
+    // This request id gets incremented (on UI thread only) when a new request to modfiy the
+    // lifecycle of an animation happens, namely when start/end/reset/reverse is called.
+    uint32_t mRequestId = 0;
 };
 
 class PropertyAnimatorSetListener : public AnimationListener {
diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp
index 0932d65..6ba0ab5 100644
--- a/libs/hwui/PropertyValuesHolder.cpp
+++ b/libs/hwui/PropertyValuesHolder.cpp
@@ -25,7 +25,27 @@
 
 using namespace VectorDrawable;
 
-float PropertyValuesHolder::getValueFromData(float fraction) {
+inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) {
+    return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction);
+}
+
+// TODO: Add a test for this
+void ColorEvaluator::evaluate(SkColor* outColor,
+        const SkColor& fromColor, const SkColor& toColor, float fraction) const {
+    U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction);
+    U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction);
+    U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction);
+    U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction);
+    *outColor = SkColorSetARGB(alpha, red, green, blue);
+}
+
+void PathEvaluator::evaluate(PathData* out,
+        const PathData& from, const PathData& to, float fraction) const {
+    VectorDrawableUtils::interpolatePaths(out, from, to, fraction);
+}
+
+template<typename T>
+const T PropertyValuesHolderImpl<T>::getValueFromData(float fraction) const {
     if (mDataSource.size() == 0) {
         LOG_ALWAYS_FATAL("No data source is defined");
         return 0;
@@ -41,57 +61,44 @@
     int lowIndex = floor(fraction);
     fraction -= lowIndex;
 
-    float value = mDataSource[lowIndex] * (1.0f - fraction)
-            + mDataSource[lowIndex + 1] * fraction;
+    T value;
+    mEvaluator->evaluate(&value, mDataSource[lowIndex], mDataSource[lowIndex + 1], fraction);
     return value;
 }
 
-void GroupPropertyValuesHolder::setFraction(float fraction) {
-    float animatedValue;
+template<typename T>
+const T PropertyValuesHolderImpl<T>::calculateAnimatedValue(float fraction) const {
     if (mDataSource.size() > 0) {
-        animatedValue = getValueFromData(fraction);
+        return getValueFromData(fraction);
     } else {
-        animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+        T value;
+        mEvaluator->evaluate(&value, mStartValue, mEndValue, fraction);
+        return value;
     }
+}
+
+void GroupPropertyValuesHolder::setFraction(float fraction) {
+    float animatedValue = calculateAnimatedValue(fraction);
     mGroup->mutateProperties()->setPropertyValue(mPropertyId, animatedValue);
 }
 
-inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) {
-    return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction);
-}
-
-// TODO: Add a test for this
-SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor,
-        float fraction) {
-    U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction);
-    U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction);
-    U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction);
-    U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction);
-    return SkColorSetARGB(alpha, red, green, blue);
-}
-
 void FullPathColorPropertyValuesHolder::setFraction(float fraction) {
-    SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction);
+    SkColor animatedValue = calculateAnimatedValue(fraction);
     mFullPath->mutateProperties()->setColorPropertyValue(mPropertyId, animatedValue);
 }
 
 void FullPathPropertyValuesHolder::setFraction(float fraction) {
-    float animatedValue;
-    if (mDataSource.size() > 0) {
-        animatedValue = getValueFromData(fraction);
-    } else {
-        animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
-    }
+    float animatedValue = calculateAnimatedValue(fraction);
     mFullPath->mutateProperties()->setPropertyValue(mPropertyId, animatedValue);
 }
 
 void PathDataPropertyValuesHolder::setFraction(float fraction) {
-    VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction);
+    mEvaluator->evaluate(&mPathData, mStartValue, mEndValue, fraction);
     mPath->mutateProperties()->setData(mPathData);
 }
 
 void RootAlphaPropertyValuesHolder::setFraction(float fraction) {
-    float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+    float animatedValue = calculateAnimatedValue(fraction);
     mTree->mutateProperties()->setRootAlpha(animatedValue);
 }
 
diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h
index b905fae..432f8ba 100644
--- a/libs/hwui/PropertyValuesHolder.h
+++ b/libs/hwui/PropertyValuesHolder.h
@@ -31,91 +31,130 @@
 class ANDROID_API PropertyValuesHolder {
 public:
     virtual void setFraction(float fraction) = 0;
-    void setPropertyDataSource(float* dataSource, int length) {
-        mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length);
-    }
-   float getValueFromData(float fraction);
-   virtual ~PropertyValuesHolder() {}
-protected:
-   std::vector<float> mDataSource;
+    virtual ~PropertyValuesHolder() {}
 };
 
-class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder {
+template <typename T>
+class Evaluator {
+public:
+    virtual void evaluate(T* out, const T& from, const T& to, float fraction) const {};
+    virtual ~Evaluator() {}
+};
+
+class FloatEvaluator : public Evaluator<float> {
+public:
+    virtual void evaluate(float* out, const float& from, const float& to, float fraction)
+            const override {
+        *out = from * (1 - fraction) + to * fraction;
+    }
+};
+
+class ANDROID_API ColorEvaluator : public Evaluator<SkColor> {
+public:
+    virtual void evaluate(SkColor* outColor, const SkColor& from, const SkColor& to,
+            float fraction) const override;
+};
+
+class ANDROID_API PathEvaluator : public Evaluator<PathData> {
+    virtual void evaluate(PathData* out, const PathData& from, const PathData& to, float fraction)
+            const override;
+};
+
+template <typename T>
+class ANDROID_API PropertyValuesHolderImpl : public PropertyValuesHolder {
+public:
+    PropertyValuesHolderImpl(const T& startValue, const T& endValue)
+            : mStartValue(startValue)
+            , mEndValue(endValue) {}
+    void setPropertyDataSource(T* dataSource, int length) {
+        mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length);
+    }
+    // Calculate the animated value from the data source.
+    const T getValueFromData(float fraction) const;
+    // Convenient method to favor getting animated value from data source. If no data source is set
+    // fall back to linear interpolation.
+    const T calculateAnimatedValue(float fraction) const;
+protected:
+   std::unique_ptr<Evaluator<T>> mEvaluator = nullptr;
+   // This contains uniformly sampled data throughout the animation duration. The first element
+   // should be the start value and the last should be the end value of the animation. When the
+   // data source is set, we'll favor data source over the linear interpolation of start/end value
+   // for calculation of animated value.
+   std::vector<T> mDataSource;
+   T mStartValue;
+   T mEndValue;
+};
+
+class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolderImpl<float> {
 public:
     GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue,
             float endValue)
-            : mGroup(ptr)
-            , mPropertyId(propertyId)
-            , mStartValue(startValue)
-            , mEndValue(endValue){
+            : PropertyValuesHolderImpl(startValue, endValue)
+            , mGroup(ptr)
+            , mPropertyId(propertyId) {
+        mEvaluator.reset(new FloatEvaluator());
     }
     void setFraction(float fraction) override;
 private:
     VectorDrawable::Group* mGroup;
     int mPropertyId;
-    float mStartValue;
-    float mEndValue;
 };
 
-class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder {
+class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolderImpl<SkColor> {
 public:
-    FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue,
-            int32_t endValue)
-            : mFullPath(ptr)
-            , mPropertyId(propertyId)
-            , mStartValue(startValue)
-            , mEndValue(endValue) {};
+    FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId,
+            SkColor startValue, SkColor endValue)
+            : PropertyValuesHolderImpl(startValue, endValue)
+            , mFullPath(ptr)
+            , mPropertyId(propertyId) {
+        mEvaluator.reset(new ColorEvaluator());
+    }
     void setFraction(float fraction) override;
     static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction);
 private:
     VectorDrawable::FullPath* mFullPath;
     int mPropertyId;
-    int32_t mStartValue;
-    int32_t mEndValue;
 };
 
-class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder {
+class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolderImpl<float> {
 public:
     FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue,
             float endValue)
-            : mFullPath(ptr)
-            , mPropertyId(propertyId)
-            , mStartValue(startValue)
-            , mEndValue(endValue) {};
+            : PropertyValuesHolderImpl(startValue, endValue)
+            , mFullPath(ptr)
+            , mPropertyId(propertyId) {
+        mEvaluator.reset(new FloatEvaluator());
+    };
     void setFraction(float fraction) override;
 private:
     VectorDrawable::FullPath* mFullPath;
     int mPropertyId;
-    float mStartValue;
-    float mEndValue;
 };
 
-class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder {
+class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolderImpl<PathData> {
 public:
     PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue,
             PathData* endValue)
-            : mPath(ptr)
-            , mStartValue(*startValue)
-            , mEndValue(*endValue) {};
+            : PropertyValuesHolderImpl(*startValue, *endValue)
+            , mPath(ptr) {
+        mEvaluator.reset(new PathEvaluator());
+    };
     void setFraction(float fraction) override;
 private:
     VectorDrawable::Path* mPath;
     PathData mPathData;
-    PathData mStartValue;
-    PathData mEndValue;
 };
 
-class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder {
+class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolderImpl<float> {
 public:
     RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue)
-            : mTree(tree)
-            , mStartValue(startValue)
-            , mEndValue(endValue) {}
+            : PropertyValuesHolderImpl(startValue, endValue)
+            , mTree(tree) {
+        mEvaluator.reset(new FloatEvaluator());
+    }
     void setFraction(float fraction) override;
 private:
     VectorDrawable::Tree* mTree;
-    float mStartValue;
-    float mEndValue;
 };
 }
 }
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index b49f9b5..b35c926 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -440,8 +440,8 @@
 }
 
 void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
-    mDisplayList->pushStagingFunctors.push_back(tree->getFunctor());
     mDisplayList->ref(tree);
+    mDisplayList->vectorDrawables.push_back(tree);
     addOp(alloc().create_trivial<VectorDrawableOp>(
             tree,
             Rect(tree->stagingProperties()->getBounds()),
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index be2dab9..5a3300a 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -421,6 +421,16 @@
     prepareSubTree(info, childFunctorsNeedLayer, mDisplayList);
     pushLayerUpdate(info);
 
+    if (mDisplayList) {
+        for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) {
+            // If any vector drawable in the display list needs update, damage the node.
+            if (vectorDrawable->isDirty()) {
+                damageSelf(info);
+            }
+            vectorDrawable->setPropertyChangeWillBeConsumed(true);
+        }
+    }
+
     info.damageAccumulator->popTransform();
 }
 
@@ -479,8 +489,8 @@
         for (auto& iter : mDisplayList->getFunctors()) {
             (*iter.functor)(DrawGlInfo::kModeSync, nullptr);
         }
-        for (size_t i = 0; i < mDisplayList->getPushStagingFunctors().size(); i++) {
-            (*mDisplayList->getPushStagingFunctors()[i])();
+        for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) {
+            vectorDrawable->syncProperties();
         }
     }
 }
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index a5d1d4b..e67dfdd 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -673,21 +673,16 @@
     void onPropertyChanged(TreeProperties* prop);
     TreeProperties* mutateStagingProperties() { return &mStagingProperties; }
     const TreeProperties* stagingProperties() const { return &mStagingProperties; }
-    PushStagingFunctor* getFunctor() { return &mFunctor;}
 
     // This should only be called from animations on RT
     TreeProperties* mutateProperties() { return &mProperties; }
 
+    // This should always be called from RT.
+    bool isDirty() const { return mCache.dirty; }
+    bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; }
+    void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; }
+
 private:
-    class VectorDrawableFunctor : public PushStagingFunctor {
-    public:
-        VectorDrawableFunctor(Tree* tree) : mTree(tree) {}
-        virtual void operator ()() {
-            mTree->syncProperties();
-        }
-    private:
-        Tree* mTree;
-    };
 
     SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop);
     bool allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height);
@@ -704,8 +699,6 @@
     TreeProperties mProperties = TreeProperties(this);
     TreeProperties mStagingProperties = TreeProperties(this);
 
-    VectorDrawableFunctor mFunctor = VectorDrawableFunctor(this);
-
     SkPaint mPaint;
     struct Cache {
         SkBitmap bitmap;
@@ -717,6 +710,8 @@
 
     PropertyChangedListener mPropertyChangedListener
             = PropertyChangedListener(&mCache.dirty, &mStagingCache.dirty);
+
+    mutable bool mWillBeConsumed = false;
 };
 
 } // namespace VectorDrawable
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c626c54..d4956be 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -282,6 +282,7 @@
 
 void CanvasContext::stopDrawing() {
     mRenderThread.removeFrameCallback(this);
+    mAnimationContext->detachAnimators();
 }
 
 void CanvasContext::notifyFramePending() {
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index b2997df..cf76a86 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -16,13 +16,26 @@
 
 #include <gtest/gtest.h>
 
+#include "AnimationContext.h"
+#include "DamageAccumulator.h"
+#include "IContextFactory.h"
 #include "RenderNode.h"
 #include "TreeInfo.h"
+#include "renderthread/CanvasContext.h"
 #include "tests/common/TestUtils.h"
 #include "utils/Color.h"
 
 using namespace android;
 using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+class ContextFactory : public android::uirenderer::IContextFactory {
+public:
+    android::uirenderer::AnimationContext* createAnimationContext
+        (android::uirenderer::renderthread::TimeLord& clock) override {
+        return new android::uirenderer::AnimationContext(clock);
+    }
+};
 
 TEST(RenderNode, hasParents) {
     auto child = TestUtils::createNode(0, 0, 200, 400,
@@ -89,3 +102,31 @@
     TestUtils::syncHierarchyPropertiesAndDisplayList(node);
     EXPECT_EQ(0, refcnt);
 }
+
+RENDERTHREAD_TEST(RenderNode, prepareTree_nullableDisplayList) {
+    ContextFactory contextFactory;
+    CanvasContext canvasContext(renderThread, false, nullptr, &contextFactory);
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, canvasContext);
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+
+    {
+        auto nonNullDLNode = TestUtils::createNode(0, 0, 200, 400,
+                [](RenderProperties& props, TestCanvas& canvas) {
+            canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+        });
+        TestUtils::syncHierarchyPropertiesAndDisplayList(nonNullDLNode);
+        EXPECT_TRUE(nonNullDLNode->getDisplayList());
+        nonNullDLNode->prepareTree(info);
+    }
+
+    {
+        auto nullDLNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
+        TestUtils::syncHierarchyPropertiesAndDisplayList(nullDLNode);
+        EXPECT_FALSE(nullDLNode->getDisplayList());
+        nullDLNode->prepareTree(info);
+    }
+
+    canvasContext.destroy(nullptr);
+}
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
deleted file mode 100644
index 6e29d23..0000000
--- a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
deleted file mode 100644
index 6110e9e..0000000
--- a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
deleted file mode 100644
index 6cca225..0000000
--- a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
deleted file mode 100644
index 6445f2a..0000000
--- a/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
deleted file mode 100644
index 78c0294..0000000
--- a/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
deleted file mode 100644
index 2fcc3b0..0000000
--- a/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
deleted file mode 100644
index 70d35bf..0000000
--- a/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
deleted file mode 100644
index 2d9b75e..0000000
--- a/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
deleted file mode 100644
index b6ebe34..0000000
--- a/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
deleted file mode 100644
index 8b67b91..0000000
--- a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
deleted file mode 100644
index 1fa0a3d..0000000
--- a/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
deleted file mode 100644
index 175bd78..0000000
--- a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
deleted file mode 100644
index 05b27e8..0000000
--- a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
deleted file mode 100644
index 6e9f8ae..0000000
--- a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
deleted file mode 100644
index 5f3371f..0000000
--- a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
deleted file mode 100644
index 539d77f..0000000
--- a/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
deleted file mode 100644
index 3216776..0000000
--- a/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
deleted file mode 100644
index 4f381ba..0000000
--- a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
deleted file mode 100644
index c67127d..0000000
--- a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
deleted file mode 100644
index d3b356b..0000000
--- a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
deleted file mode 100644
index 2d38129..0000000
--- a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
deleted file mode 100644
index fb76575..0000000
--- a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
deleted file mode 100644
index d8b68eb..0000000
--- a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
deleted file mode 100644
index 02cc3af..0000000
--- a/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
deleted file mode 100644
index 7805b7a..0000000
--- a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
deleted file mode 100644
index 8127774..0000000
--- a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
deleted file mode 100644
index 84b8085..0000000
--- a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
deleted file mode 100644
index 289d6ac..0000000
--- a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
deleted file mode 100644
index 72bc804..0000000
--- a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
deleted file mode 100644
index e31ce2b..0000000
--- a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
deleted file mode 100644
index f23b0e7..0000000
--- a/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
deleted file mode 100644
index 1e12f96..0000000
--- a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
deleted file mode 100644
index 8b547d9..0000000
--- a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
deleted file mode 100644
index 03c5033..0000000
--- a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
deleted file mode 100644
index b9a9923..0000000
--- a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
deleted file mode 100644
index 989e1ab..0000000
--- a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
deleted file mode 100644
index de8c389..0000000
--- a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
deleted file mode 100644
index 2eb8a92..0000000
--- a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml b/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml
new file mode 100644
index 0000000..cc9b732
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml
@@ -0,0 +1,29 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36
+            1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1
+            -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45
+            0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml b/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml
new file mode 100644
index 0000000..bbe3914
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml
@@ -0,0 +1,26 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87
+            3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7c0,-4.97 -4.03,-9 -9,-9z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml b/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml
new file mode 100644
index 0000000..ceeef19
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml
@@ -0,0 +1,27 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87
+            3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9
+            -9,-9z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml b/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml
new file mode 100644
index 0000000..67b42ae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml
@@ -0,0 +1,26 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M15,7.5V2H9v5.5l3,3 3,-3zM7.5,9H2v6h5.5l3,-3 -3,-3zM9,16.5V22h6v-5.5l-3,-3
+            -3,3zM16.5,9l-3,3 3,3H22V9h-5.5z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml b/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml
new file mode 100644
index 0000000..c5ab01c
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml
@@ -0,0 +1,30 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82
+            -0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57
+            0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89
+            -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19
+            9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29
+            4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml b/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml
new file mode 100644
index 0000000..821618d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml
@@ -0,0 +1,26 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,1.07L13,9h7c0,-4.08 -3.05,-7.44 -7,-7.93zM4,15c0,4.42
+            3.58,8 8,8s8,-3.58 8,-8v-4L4,11v4zM11,1.07C7.05,1.56 4,4.92 4,9h7L11,1.07z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml b/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml
new file mode 100644
index 0000000..4aa8569
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml
@@ -0,0 +1,26 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,5L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9
+            2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM11,8h2v2h-2L11,8zM11,11h2v2h-2v-2zM8,8h2v2L8,10L8,8zM8,11h2v2L8,13v-2zM7,13L5,13v-2h2v2zM7,10L5,10L5,8h2v2zM16,17L8,17v-2h8v2zM16,13h-2v-2h2v2zM16,10h-2L14,8h2v2zM19,13h-2v-2h2v2zM19,10h-2L17,8h2v2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/settings_with_drawer.xml b/packages/SettingsLib/res/layout/settings_with_drawer.xml
index 67296a6..a68a44e 100644
--- a/packages/SettingsLib/res/layout/settings_with_drawer.xml
+++ b/packages/SettingsLib/res/layout/settings_with_drawer.xml
@@ -41,6 +41,11 @@
                 android:background="?android:attr/colorPrimary" />
         </FrameLayout>
         <FrameLayout
+            android:id="@+id/content_header_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionBarStyle" />
+        <FrameLayout
             android:id="@+id/content_frame"
             android:layout_width="match_parent"
             android:layout_height="fill_parent"
diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
index 320cd58..76ba0a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
@@ -156,7 +156,7 @@
         return intent;
     }
 
-    private static void addIntentParameters(Context context, Intent intent, String backupContext) {
+    public static void addIntentParameters(Context context, Intent intent, String backupContext) {
         if (!intent.hasExtra(EXTRA_CONTEXT)) {
             // Insert some context if none exists.
             intent.putExtra(EXTRA_CONTEXT, backupContext);
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 37e3c53..6658c14 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -41,8 +41,10 @@
 import android.view.Window;
 import android.view.WindowManager.LayoutParams;
 import android.widget.AdapterView;
+import android.widget.FrameLayout;
 import android.widget.ListView;
 import android.widget.Toolbar;
+
 import com.android.settingslib.R;
 import com.android.settingslib.applications.InterestingConfigChanges;
 
@@ -68,6 +70,7 @@
     private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
 
     private SettingsDrawerAdapter mDrawerAdapter;
+    private FrameLayout mContentHeaderContainer;
     private DrawerLayout mDrawerLayout;
     private boolean mShowingMenu;
 
@@ -84,6 +87,7 @@
             requestWindowFeature(Window.FEATURE_NO_TITLE);
         }
         super.setContentView(R.layout.settings_with_drawer);
+        mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);
         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
         if (mDrawerLayout == null) {
             return;
@@ -180,6 +184,13 @@
         }
     }
 
+    public void setContentHeaderView(View headerView) {
+        mContentHeaderContainer.removeAllViews();
+        if (headerView != null) {
+            mContentHeaderContainer.addView(headerView);
+        }
+    }
+
     @Override
     public void setContentView(@LayoutRes int layoutResID) {
         final ViewGroup parent = (ViewGroup) findViewById(R.id.content_frame);
@@ -272,6 +283,13 @@
         }
     }
 
+    public HashMap<Pair<String, String>, Tile> getTileCache() {
+        if (sTileCache == null) {
+            getDashboardCategories();
+        }
+        return sTileCache;
+    }
+
     public void onProfileTileOpen() {
         finish();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 418b138..b9c758c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -302,7 +302,7 @@
         return false;
     }
 
-    private static final Comparator<Tile> TILE_COMPARATOR =
+    public static final Comparator<Tile> TILE_COMPARATOR =
             new Comparator<Tile>() {
         @Override
         public int compare(Tile lhs, Tile rhs) {
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 978ca94..108814e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -216,4 +216,7 @@
 
     <!-- Default setting for ability to add users from the lock screen -->
     <bool name="def_add_users_from_lockscreen">false</bool>
+
+    <!-- Default setting for disallow oem unlock. -->
+    <bool name="def_oem_unlock_disallow">false</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 774be60..950c7d3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2074,7 +2074,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 127;
+            private static final int SETTINGS_VERSION = 128;
 
             private final int mUserId;
 
@@ -2329,6 +2329,18 @@
                     currentVersion = 127;
                 }
 
+                if (currentVersion == 127) {
+                    // Version 127: Disable OEM unlock setting by default on some devices.
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    String defaultOemUnlockDisabled = (getContext().getResources()
+                            .getBoolean(R.bool.def_oem_unlock_disallow) ? "1" : "0");
+                    globalSettings.insertSettingLocked(
+                            Settings.Global.OEM_UNLOCK_DISALLOWED,
+                            defaultOemUnlockDisabled,
+                            SettingsState.SYSTEM_PACKAGE_NAME);
+                    currentVersion = 128;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 // Return the current version.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 25dc357..809774b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -165,6 +165,9 @@
     <!-- the ability to rename notifications posted by other apps -->
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
 
+    <!-- shortcut manager -->
+    <uses-permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
diff --git a/packages/SystemUI/res/drawable/ic_mode_edit.xml b/packages/SystemUI/res/drawable/ic_mode_edit.xml
new file mode 100644
index 0000000..8a73686
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_mode_edit.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="20.0dp"
+        android:height="20.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.81,9.94l-3.75,-3.75L3.0,17.25zM20.71,7.04c0.39,-0.3 0.39,-1.02 0.0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0.0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml b/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml
new file mode 100644
index 0000000..736a04a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_branded_vpn.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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="12.0dp"
+        android:height="12.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M12.700000,10.000000c-0.800000,-2.300000 -3.000000,-4.000000 -5.700000,-4.000000c-3.300000,0.000000 -6.000000,2.700000 -6.000000,6.000000s2.700000,6.000000 6.000000,6.000000c2.600000,0.000000 4.800000,-1.700000 5.700000,-4.000000L17.000000,14.000000l0.000000,4.000000l4.000000,0.000000l0.000000,-4.000000l2.000000,0.000000l0.000000,-4.000000L12.700000,10.000000zM7.000000,14.000000c-1.100000,0.000000 -2.000000,-0.900000 -2.000000,-2.000000c0.000000,-1.100000 0.900000,-2.000000 2.000000,-2.000000s2.000000,0.900000 2.000000,2.000000C9.000000,13.100000 8.100000,14.000000 7.000000,14.000000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_20dp.xml b/packages/SystemUI/res/drawable/ic_settings_20dp.xml
new file mode 100644
index 0000000..3170f86
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_settings_20dp.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M19.4,13.0c0.0,-0.3 0.1,-0.6 0.1,-1.0s0.0,-0.7 -0.1,-1.0l2.1,-1.7c0.2,-0.2 0.2,-0.4 0.1,-0.6l-2.0,-3.5C19.5,5.1 19.3,5.0 19.0,5.1l-2.5,1.0c-0.5,-0.4 -1.1,-0.7 -1.7,-1.0l-0.4,-2.6C14.5,2.2 14.2,2.0 14.0,2.0l-4.0,0.0C9.8,2.0 9.5,2.2 9.5,2.4L9.1,5.1C8.5,5.3 8.0,5.7 7.4,6.1L5.0,5.1C4.7,5.0 4.5,5.1 4.3,5.3l-2.0,3.5C2.2,8.9 2.3,9.2 2.5,9.4L4.6,11.0c0.0,0.3 -0.1,0.6 -0.1,1.0s0.0,0.7 0.1,1.0l-2.1,1.7c-0.2,0.2 -0.2,0.4 -0.1,0.6l2.0,3.5C4.5,18.9 4.7,19.0 5.0,18.9l2.5,-1.0c0.5,0.4 1.1,0.7 1.7,1.0l0.4,2.6c0.0,0.2 0.2,0.4 0.5,0.4l4.0,0.0c0.2,0.0 0.5,-0.2 0.5,-0.4l0.4,-2.6c0.6,-0.3 1.2,-0.6 1.7,-1.0l2.5,1.0c0.2,0.1 0.5,0.0 0.6,-0.2l2.0,-3.5c0.1,-0.2 0.1,-0.5 -0.1,-0.6L19.4,13.0zM12.0,15.5c-1.9,0.0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5s3.5,1.6 3.5,3.5S13.9,15.5 12.0,15.5z"
+        android:fillColor="#ffffffff" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml b/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml
new file mode 100644
index 0000000..a86e5b9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_branded_vpn.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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17.0dp"
+        android:height="17.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12.700000,10.000000c-0.800000,-2.300000 -3.000000,-4.000000 -5.700000,-4.000000c-3.300000,0.000000 -6.000000,2.700000 -6.000000,6.000000s2.700000,6.000000 6.000000,6.000000c2.600000,0.000000 4.800000,-1.700000 5.700000,-4.000000L17.000000,14.000000l0.000000,4.000000l4.000000,0.000000l0.000000,-4.000000l2.000000,0.000000l0.000000,-4.000000L12.700000,10.000000zM7.000000,14.000000c-1.100000,0.000000 -2.000000,-0.900000 -2.000000,-2.000000c0.000000,-1.100000 0.900000,-2.000000 2.000000,-2.000000s2.000000,0.900000 2.000000,2.000000C9.000000,13.100000 8.100000,14.000000 7.000000,14.000000z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
index ee55ec2..8b6060f 100644
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
+++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
@@ -37,19 +37,6 @@
             android:importantForAccessibility="yes"
             android:focusable="true" />
 
-        <TextView
-            android:id="@android:id/edit"
-            style="@style/QSBorderlessButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="end"
-            android:minWidth="88dp"
-            android:textAppearance="@style/TextAppearance.QS.DetailButton"
-            android:textColor="#64FFFFFF"
-            android:focusable="true"
-            android:text="@string/qs_edit"
-            android:contentDescription="@string/accessibility_quick_settings_edit"/>
-
     </FrameLayout>
 
 </com.android.systemui.qs.PagedTileLayout>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 751f181..26c7339 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -26,7 +26,7 @@
     <com.android.systemui.qs.QSPanel
             android:id="@+id/quick_settings_panel"
             android:background="#0000"
-            android:layout_marginTop="@dimen/status_bar_header_height"
+            android:layout_marginTop="80dp"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:paddingBottom="8dp" />
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index f65a667..103e0b0 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -37,7 +37,7 @@
         android:clipToPadding="false"
         android:orientation="horizontal"
         android:layout_alignParentEnd="true"
-        android:layout_marginTop="28dp"
+        android:layout_marginTop="4dp"
         android:layout_marginEnd="12dp">
 
         <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
@@ -52,6 +52,18 @@
                 android:scaleType="centerInside"/>
         </com.android.systemui.statusbar.phone.MultiUserSwitch>
 
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@android:id/edit"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:clipToPadding="false"
+            android:clickable="true"
+            android:focusable="true"
+            android:src="@drawable/ic_mode_edit"
+            android:background="?android:attr/selectableItemBackgroundBorderless"
+            android:contentDescription="@string/accessibility_quick_settings_edit"
+            android:padding="14dp" />
+
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
             android:id="@+id/settings_button_container"
             android:layout_width="48dp"
@@ -64,7 +76,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:background="@drawable/ripple_drawable"
-                android:src="@drawable/ic_settings"
+                android:src="@drawable/ic_settings_20dp"
                 android:contentDescription="@string/accessibility_quick_settings_settings" />
             <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/tuner_icon"
                 android:layout_width="match_parent"
@@ -98,7 +110,7 @@
         android:layout_alignParentTop="true"
         android:paddingStart="16dp"
         android:paddingEnd="16dp"
-        android:paddingTop="8dp"
+        android:paddingTop="2dp"
         android:visibility="gone"
         android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
         android:text="@*android:string/emergency_calls_only"
@@ -109,6 +121,7 @@
     <include
         android:id="@+id/date_time_alarm_group"
         layout="@layout/status_bar_alarm_group"
+        android:layout_marginTop="16dp"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true" />
 
@@ -116,7 +129,7 @@
         android:id="@+id/quick_qs_panel"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="28dp"
+        android:layout_marginTop="52dp"
         android:layout_marginStart="12dp"
         android:layout_marginEnd="12dp"
         android:layout_alignParentEnd="true"
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 6c5a313..7e63cbf 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -94,8 +94,6 @@
     <dimen name="navigation_key_width">128dp</dimen>
     <dimen name="navigation_key_padding">25dp</dimen>
 
-    <dimen name="qs_expand_margin">0dp</dimen>
-
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">488dp</dimen>
 
diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml
index 4160c83..eaca9d7 100644
--- a/packages/SystemUI/res/values-w550dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-w550dp-land/dimens.xml
@@ -18,6 +18,4 @@
 <resources>
     <!-- Standard notification width + gravity -->
     <dimen name="notification_panel_width">544dp</dimen>
-
-    <dimen name="qs_expand_margin">32dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 02b1b50..134388f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -103,9 +103,19 @@
         wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location
     </string>
 
+    <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
+    <string name="quick_settings_tiles_stock" translatable="false">
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night
+    </string>
+
     <!-- The tiles to display in QuickSettings -->
     <string name="quick_settings_tiles" translatable="false">default</string>
 
+    <!-- The tiles to display in QuickSettings in retail mode -->
+    <string name="quick_settings_tiles_retail_mode" translatable="false">
+        cell,battery,dnd,flashlight,rotation,location
+    </string>
+
     <!-- Whether or not the RSSI tile is capitalized or not. -->
     <bool name="quick_settings_rssi_tile_capitalization">true</bool>
 
@@ -265,4 +275,3 @@
     <bool name="quick_settings_show_full_alarm">false</bool>
 
 </resources>
-
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2a4752a..eef5717 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -130,7 +130,7 @@
     <dimen name="close_handle_underlap">32dp</dimen>
 
     <!-- Height of the status bar header bar -->
-    <dimen name="status_bar_header_height">80dp</dimen>
+    <dimen name="status_bar_header_height">104dp</dimen>
 
     <!-- Height of the status bar header bar when expanded -->
     <dimen name="status_bar_header_height_expanded">116dp</dimen>
@@ -172,8 +172,6 @@
     <dimen name="qs_tile_margin_top">16dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
     <dimen name="qs_quick_tile_padding">12dp</dimen>
-    <dimen name="qs_date_anim_translation">32dp</dimen>
-    <dimen name="qs_date_alarm_anim_translation">22dp</dimen>
     <dimen name="qs_date_collapsed_text_size">14sp</dimen>
     <dimen name="qs_date_text_size">16sp</dimen>
     <dimen name="qs_header_gear_translation">16dp</dimen>
@@ -202,7 +200,6 @@
     <dimen name="qs_detail_margin_top">28dp</dimen>
     <dimen name="qs_data_usage_text_size">14sp</dimen>
     <dimen name="qs_data_usage_usage_text_size">36sp</dimen>
-    <dimen name="qs_expand_margin">0dp</dimen>
     <dimen name="qs_battery_padding">2dp</dimen>
     <dimen name="qs_detail_items_padding_top">4dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 17f4dab..fd3bd92 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -36,6 +36,7 @@
     public static final Interpolator ACCELERATE = new AccelerateInterpolator();
     public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
     public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
+    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
 
     /**
      * Interpolator to be used when animating a move based on a click. Pair with enough duration.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 53c2233..9a36aca 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -23,6 +23,9 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.R;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -115,4 +118,8 @@
     public <T> T createInstance(Class<T> classType) {
         return null;
     }
+
+    public AssistManager createAssistManager(BaseStatusBar bar, Context context) {
+        return new AssistManager(bar, context);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index 5878219..8bd38db 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -95,39 +95,34 @@
     private class AssistDisclosureView extends View
             implements ValueAnimator.AnimatorUpdateListener {
 
-        public static final int TRACING_ANIMATION_DURATION = 600;
-        public static final int ALPHA_IN_ANIMATION_DURATION = 450;
-        public static final int ALPHA_OUT_ANIMATION_DURATION = 400;
+        static final int FULL_ALPHA = 222; // 87%
+        static final int ALPHA_IN_ANIMATION_DURATION = 400;
+        static final int ALPHA_OUT_ANIMATION_DURATION = 300;
+
 
         private float mThickness;
         private float mShadowThickness;
         private final Paint mPaint = new Paint();
         private final Paint mShadowPaint = new Paint();
 
-        private final ValueAnimator mTracingAnimator;
         private final ValueAnimator mAlphaOutAnimator;
         private final ValueAnimator mAlphaInAnimator;
         private final AnimatorSet mAnimator;
 
-        private float mTracingProgress = 0;
         private int mAlpha = 0;
 
         public AssistDisclosureView(Context context) {
             super(context);
 
-            mTracingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(TRACING_ANIMATION_DURATION);
-            mTracingAnimator.addUpdateListener(this);
-            mTracingAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext,
-                    R.interpolator.assist_disclosure_trace));
-            mAlphaInAnimator = ValueAnimator.ofInt(0, 255).setDuration(ALPHA_IN_ANIMATION_DURATION);
+            mAlphaInAnimator = ValueAnimator.ofInt(0, FULL_ALPHA)
+                    .setDuration(ALPHA_IN_ANIMATION_DURATION);
             mAlphaInAnimator.addUpdateListener(this);
-            mAlphaInAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-            mAlphaOutAnimator = ValueAnimator.ofInt(255, 0).setDuration(
+            mAlphaInAnimator.setInterpolator(Interpolators.CUSTOM_40_40);
+            mAlphaOutAnimator = ValueAnimator.ofInt(FULL_ALPHA, 0).setDuration(
                     ALPHA_OUT_ANIMATION_DURATION);
             mAlphaOutAnimator.addUpdateListener(this);
-            mAlphaOutAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+            mAlphaOutAnimator.setInterpolator(Interpolators.CUSTOM_40_40);
             mAnimator = new AnimatorSet();
-            mAnimator.play(mAlphaInAnimator).with(mTracingAnimator);
             mAnimator.play(mAlphaInAnimator).before(mAlphaOutAnimator);
             mAnimator.addListener(new AnimatorListenerAdapter() {
                 boolean mCancelled;
@@ -174,8 +169,6 @@
             super.onDetachedFromWindow();
 
             mAnimator.cancel();
-
-            mTracingProgress = 0;
             mAlpha = 0;
         }
 
@@ -197,45 +190,32 @@
             final int width = getWidth();
             final int height = getHeight();
             float thickness = mThickness;
-            final float pixelProgress = mTracingProgress * (width + height - 2 * thickness);
 
-            float bottomProgress = Math.min(pixelProgress, width / 2f);
-            if (bottomProgress > 0) {
-                drawBeam(canvas,
-                        width / 2f - bottomProgress,
-                        height - thickness,
-                        width / 2f + bottomProgress,
-                        height, paint, padding);
-            }
+            // bottom
+            drawBeam(canvas,
+                    0,
+                    height - thickness,
+                    width,
+                    height, paint, padding);
 
-            float sideProgress = Math.min(pixelProgress - bottomProgress, height - thickness);
-            if (sideProgress > 0) {
-                drawBeam(canvas,
-                        0,
-                        (height - thickness) - sideProgress,
-                        thickness,
-                        height - thickness, paint, padding);
-                drawBeam(canvas,
-                        width - thickness,
-                        (height - thickness) - sideProgress,
-                        width,
-                        height - thickness, paint, padding);
-            }
+            // sides
+            drawBeam(canvas,
+                    0,
+                    0,
+                    thickness,
+                    height - thickness, paint, padding);
+            drawBeam(canvas,
+                    width - thickness,
+                    0,
+                    width,
+                    height - thickness, paint, padding);
 
-            float topProgress = Math.min(pixelProgress - bottomProgress - sideProgress,
-                    width / 2 - thickness);
-            if (sideProgress > 0 && topProgress > 0) {
-                drawBeam(canvas,
-                        thickness,
-                        0,
-                        thickness + topProgress,
-                        thickness, paint, padding);
-                drawBeam(canvas,
-                        (width - thickness) - topProgress,
-                        0,
-                        width - thickness,
-                        thickness, paint, padding);
-            }
+            // top
+            drawBeam(canvas,
+                    thickness,
+                    0,
+                    width - thickness,
+                    thickness, paint, padding);
         }
 
         private void drawBeam(Canvas canvas, float left, float top, float right, float bottom,
@@ -253,8 +233,6 @@
                 mAlpha = (int) mAlphaOutAnimator.getAnimatedValue();
             } else if (animation == mAlphaInAnimator) {
                 mAlpha = (int) mAlphaInAnimator.getAnimatedValue();
-            } else if (animation == mTracingAnimator) {
-                mTracingProgress = (float) mTracingAnimator.getAnimatedValue();
             }
             invalidate();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index a0aeb2f..a5e771f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -30,6 +30,7 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -103,6 +104,10 @@
         }
     }
 
+    protected boolean shouldShowOrb() {
+        return true;
+    }
+
     public void startAssist(Bundle args) {
         final ComponentName assistComponent = getAssistInfo();
         if (assistComponent == null) {
@@ -110,7 +115,7 @@
         }
 
         final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
-        if (!isService || !isVoiceSessionRunning()) {
+        if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) {
             showOrb(assistComponent, isService);
             mView.postDelayed(mHideRunnable, isService
                     ? TIMEOUT_SERVICE
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 3370091..5f1b042 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -39,6 +39,8 @@
 import android.util.Log;
 import android.view.Display;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.DozeParameters.PulseSchedule;
@@ -538,6 +540,10 @@
             mWakeLock.acquire();
             try {
                 if (DEBUG) Log.d(mTag, "onTrigger: " + triggerEventToString(event));
+                if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
+                    int subType = (int) event.values[0];
+                    MetricsLogger.action(mContext, MetricsEvent.ACTION_AMBIENT_GESTURE, subType);
+                }
                 if (mDebugVibrate) {
                     final Vibrator v = (Vibrator) mContext.getSystemService(
                             Context.VIBRATOR_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index cad7f64..3bbcf6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -360,7 +360,7 @@
             mSwitchingUser = false;
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
-                if (info != null && info.isGuest()) {
+                if (info != null && (info.isGuest() || info.isDemo())) {
                     // If we just switched to a guest, try to dismiss keyguard.
                     dismiss();
                 }
@@ -399,8 +399,7 @@
             sendUserPresentBroadcast();
             synchronized (KeyguardViewMediator.this) {
                 // If system user is provisioned, we might want to lock now to avoid showing launcher
-                if (UserManager.isSplitSystemUser()
-                        && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM) {
+                if (mustNotUnlockCurrentUser()) {
                     doKeyguardLocked(null);
                 }
             }
@@ -599,7 +598,6 @@
                 return KeyguardSecurityView.PROMPT_REASON_WRONG_CREDENTIAL;
             }
 
-
             return KeyguardSecurityView.PROMPT_REASON_NONE;
         }
     };
@@ -608,6 +606,11 @@
         mPM.userActivity(SystemClock.uptimeMillis(), false);
     }
 
+    boolean mustNotUnlockCurrentUser() {
+        return (UserManager.isSplitSystemUser() || UserManager.isDeviceInDemoMode(mContext))
+                && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM;
+    }
+
     private void setupLocked() {
         mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWM = WindowManagerGlobal.getWindowManagerService();
@@ -1176,8 +1179,7 @@
         }
 
         // In split system user mode, we never unlock system user.
-        if (!UserManager.isSplitSystemUser()
-                || KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM
+        if (!mustNotUnlockCurrentUser()
                 || !mUpdateMonitor.isDeviceProvisioned()) {
 
             // if the setup wizard hasn't run yet, don't show
@@ -1615,8 +1617,7 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
 
-            if (UserManager.isSplitSystemUser()
-                    && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM) {
+            if (mustNotUnlockCurrentUser()) {
                 // In split system user mode, we never unlock system user. The end user has to
                 // switch to another user.
                 // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 932b4f5..65d6805 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -57,6 +58,7 @@
     private boolean mIsVisible;
     private boolean mIsIconVisible;
     private int mFooterTextId;
+    private int mFooterIconId;
 
     public QSFooter(QSPanel qsPanel, Context context) {
         mRootView = LayoutInflater.from(context)
@@ -64,6 +66,7 @@
         mRootView.setOnClickListener(this);
         mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
         mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon);
+        mFooterIconId = R.drawable.ic_qs_vpn;
         mContext = context;
         mMainHandler = new Handler();
     }
@@ -118,6 +121,14 @@
             mIsVisible = true;
         } else {
             mFooterTextId = R.string.vpn_footer;
+            // Update the VPN footer icon, if needed.
+            int footerIconId = (mSecurityController.isVpnBranded()
+                ? R.drawable.ic_qs_branded_vpn
+                : R.drawable.ic_qs_vpn);
+            if (mFooterIconId != footerIconId) {
+                mFooterIcon.setImageResource(footerIconId);
+                mFooterIconId = footerIconId;
+            }
             mIsVisible = mIsIconVisible;
         }
         mMainHandler.post(mUpdateDisplayState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 2c874e5..109d2c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -104,8 +104,6 @@
                 R.layout.qs_paged_tile_layout, this, false);
         mTileLayout.setListening(mListening);
         addView((View) mTileLayout);
-        findViewById(android.R.id.edit).setOnClickListener(view ->
-                mHost.startRunnableDismissingKeyguard(() -> showEdit(view)));
     }
 
     public boolean isShowingCustomize() {
@@ -363,7 +361,7 @@
     }
 
 
-    private void showEdit(final View v) {
+    public void showEdit(final View v) {
         v.post(new Runnable() {
             @Override
             public void run() {
@@ -371,8 +369,8 @@
                     if (!mCustomizePanel.isCustomizing()) {
                         int[] loc = new int[2];
                         v.getLocationInWindow(loc);
-                        int x = loc[0];
-                        int y = loc[1];
+                        int x = loc[0] + v.getWidth() / 2;
+                        int y = loc[1] + v.getHeight() / 2;
                         mCustomizePanel.show(x, y);
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index c984abe..afc987e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -141,12 +141,11 @@
     };
 
     public int getNumQuickTiles(Context context) {
-        return TunerService.get(context).getValue(NUM_QUICK_TILES, 5);
+        return TunerService.get(context).getValue(NUM_QUICK_TILES, 6);
     }
 
     private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
 
-        private final Space mEndSpacer;
         protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
         private boolean mListening;
 
@@ -156,25 +155,6 @@
             setClipToPadding(false);
             setGravity(Gravity.CENTER_VERTICAL);
             setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-
-            mEndSpacer = new Space(context);
-            mEndSpacer.setLayoutParams(generateLayoutParams());
-            updateDownArrowMargin();
-            addView(mEndSpacer);
-            setOrientation(LinearLayout.HORIZONTAL);
-        }
-
-        @Override
-        protected void onConfigurationChanged(Configuration newConfig) {
-            super.onConfigurationChanged(newConfig);
-            updateDownArrowMargin();
-        }
-
-        private void updateDownArrowMargin() {
-            LayoutParams params = (LayoutParams) mEndSpacer.getLayoutParams();
-            params.setMarginStart(mContext.getResources().getDimensionPixelSize(
-                    R.dimen.qs_expand_margin));
-            mEndSpacer.setLayoutParams(params);
         }
 
         @Override
@@ -188,11 +168,11 @@
 
         @Override
         public void addTile(TileRecord tile) {
-            addView(tile.tileView, getChildCount() - 1 /* Leave icon at end */,
-                    generateLayoutParams());
-            // Add a spacer.
-            addView(new Space(mContext), getChildCount() - 1 /* Leave icon at end */,
-                    generateSpaceParams());
+            if (getChildCount() != 0) {
+                // Add a spacer.
+                addView(new Space(mContext), getChildCount(), generateSpaceParams());
+            }
+            addView(tile.tileView, getChildCount(), generateLayoutParams());
             mRecords.add(tile);
             tile.tile.setListening(this, mListening);
         }
@@ -217,8 +197,10 @@
             int childIndex = getChildIndex(tile.tileView);
             // Remove the tile.
             removeViewAt(childIndex);
-            // Remove its spacer as well.
-            removeViewAt(childIndex);
+            if (getChildCount() != 0) {
+                // Remove its spacer as well.
+                removeViewAt(childIndex);
+            }
             mRecords.remove(tile);
             tile.tile.setListening(this, false);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 777ed6a..3d192e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -57,8 +57,7 @@
     }
 
     private void addSystemTiles(final QSTileHost host) {
-        String possible = mContext.getString(R.string.quick_settings_tiles_default)
-                + ",hotspot,inversion,saver,work,cast,night";
+        String possible = mContext.getString(R.string.quick_settings_tiles_stock);
         String[] possibleTiles = possible.split(",");
         final Handler qsHandler = new Handler(host.getLooper());
         final Handler mainHandler = new Handler(Looper.getMainLooper());
@@ -145,9 +144,16 @@
             PackageManager pm = mContext.getPackageManager();
             List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                     new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+            String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
             for (ResolveInfo info : services) {
                 String packageName = info.serviceInfo.packageName;
                 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+
+                // Don't include apps that are a part of the default tile set.
+                if (stockTiles.contains(componentName.flattenToString())) {
+                    continue;
+                }
+
                 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
                 String spec = CustomTile.toSpec(componentName);
                 State state = getState(params[0], spec);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 79e06c6..9211562 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -72,7 +72,6 @@
     private static final String TAG = KeyboardShortcuts.class.getSimpleName();
     private static final Object sLock = new Object();
     private static KeyboardShortcuts sInstance;
-    private static boolean sIsShowing;
 
     private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
     private final SparseArray<String> mModifierNames = new SparseArray<>();
@@ -131,13 +130,12 @@
                 dismiss();
             }
             getInstance(context).showKeyboardShortcuts(deviceId);
-            sIsShowing = true;
         }
     }
 
     public static void toggle(Context context, int deviceId) {
         synchronized (sLock) {
-            if (sIsShowing) {
+            if (isShowing()) {
                 dismiss();
             } else {
                 show(context, deviceId);
@@ -151,10 +149,14 @@
                 sInstance.dismissKeyboardShortcuts();
                 sInstance = null;
             }
-            sIsShowing = false;
         }
     }
 
+    private static boolean isShowing() {
+        return sInstance != null && sInstance.mKeyboardShortcutsDialog != null
+                && sInstance.mKeyboardShortcutsDialog.isShowing();
+    }
+
     private void loadResources(Context context) {
         mSpecialCharacterNames.put(
                 KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index dba7130..bfa43fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -42,6 +42,7 @@
     private float mViewAlpha = 1.0f;
     private ValueAnimator mAlphaAnimator;
     private Rect mExcludedRect = new Rect();
+    private int mLeftInset = 0;
     private boolean mHasExcludedArea;
     private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener
             = new ValueAnimator.AnimatorUpdateListener() {
@@ -87,12 +88,12 @@
                 if (mExcludedRect.top > 0) {
                     canvas.drawRect(0, 0, getWidth(), mExcludedRect.top, mPaint);
                 }
-                if (mExcludedRect.left > 0) {
-                    canvas.drawRect(0,  mExcludedRect.top, mExcludedRect.left, mExcludedRect.bottom,
-                            mPaint);
+                if (mExcludedRect.left + mLeftInset > 0) {
+                    canvas.drawRect(0,  mExcludedRect.top, mExcludedRect.left + mLeftInset,
+                            mExcludedRect.bottom, mPaint);
                 }
-                if (mExcludedRect.right < getWidth()) {
-                    canvas.drawRect(mExcludedRect.right,
+                if (mExcludedRect.right + mLeftInset < getWidth()) {
+                    canvas.drawRect(mExcludedRect.right + mLeftInset,
                             mExcludedRect.top,
                             getWidth(),
                             mExcludedRect.bottom,
@@ -183,4 +184,14 @@
     public void setChangeRunnable(Runnable changeRunnable) {
         mChangeRunnable = changeRunnable;
     }
+
+    public void setLeftInset(int leftInset) {
+        if (mLeftInset != leftInset) {
+            mLeftInset = leftInset;
+
+            if (mHasExcludedArea) {
+                invalidate();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 2b59c68..a2ad46a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -67,6 +67,8 @@
 
     private boolean mNoSimsVisible = false;
     private boolean mVpnVisible = false;
+    private int mVpnIconId = 0;
+    private int mLastVpnIconId = -1;
     private boolean mEthernetVisible = false;
     private int mEthernetIconId = 0;
     private int mLastEthernetIconId = -1;
@@ -164,6 +166,7 @@
         mSC = sc;
         mSC.addCallback(this);
         mVpnVisible = mSC.isVpnEnabled();
+        mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
     }
 
     @Override
@@ -241,6 +244,7 @@
             @Override
             public void run() {
                 mVpnVisible = mSC.isVpnEnabled();
+                mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
                 apply();
             }
         });
@@ -428,6 +432,15 @@
         if (mWifiGroup == null) return;
 
         mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE);
+        if (mVpnVisible) {
+            if (mLastVpnIconId != mVpnIconId) {
+                setIconForView(mVpn, mVpnIconId);
+                mLastVpnIconId = mVpnIconId;
+            }
+            mVpn.setVisibility(View.VISIBLE);
+        } else {
+            mVpn.setVisibility(View.GONE);
+        }
         if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
 
         if (mEthernetVisible) {
@@ -556,6 +569,10 @@
         v.setImageTintList(ColorStateList.valueOf(tint));
     }
 
+    private int currentVpnIconId(boolean isBranded) {
+        return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
+    }
+
     private class PhoneState {
         private final int mSubId;
         private boolean mMobileVisible = false;
@@ -677,4 +694,3 @@
         }
     }
 }
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index 30d24ff..b5a48a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -14,11 +14,10 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
 import android.view.View;
-import android.widget.ImageView;
-
-import com.android.systemui.statusbar.policy.KeyButtonView;
 
 import java.util.ArrayList;
 
@@ -65,9 +64,9 @@
             view.setVisibility(mVisibility);
         }
         if (mImageResource > 0) {
-            ((ImageView) view).setImageResource(mImageResource);
+            ((ButtonInterface) view).setImageResource(mImageResource);
         } else if (mImageDrawable != null) {
-            ((ImageView) view).setImageDrawable(mImageDrawable);
+            ((ButtonInterface) view).setImageDrawable(mImageDrawable);
         }
     }
 
@@ -88,7 +87,7 @@
         mImageResource = -1;
         final int N = mViews.size();
         for (int i = 0; i < N; i++) {
-            ((ImageView) mViews.get(i)).setImageDrawable(mImageDrawable);
+            ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable);
         }
     }
 
@@ -97,7 +96,7 @@
         mImageDrawable = null;
         final int N = mViews.size();
         for (int i = 0; i < N; i++) {
-            ((ImageView) mViews.get(i)).setImageResource(mImageResource);
+            ((ButtonInterface) mViews.get(i)).setImageResource(mImageResource);
         }
     }
 
@@ -114,7 +113,7 @@
         // This seems to be an instantaneous thing, so not going to persist it.
         final int N = mViews.size();
         for (int i = 0; i < N; i++) {
-            ((KeyButtonView) mViews.get(i)).abortCurrentGesture();
+            ((ButtonInterface) mViews.get(i)).abortCurrentGesture();
         }
     }
 
@@ -165,4 +164,15 @@
     public void setCurrentView(View currentView) {
         mCurrentView = currentView.findViewById(mId);
     }
+
+    /**
+     * Interface for ImageView button actions.
+     */
+    public interface ButtonInterface {
+        void setImageResource(@DrawableRes int resId);
+
+        void setImageDrawable(@Nullable Drawable drawable);
+
+        void abortCurrentGesture();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 2bee816..dd46b08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -26,7 +26,9 @@
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.Space;
+
 import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 import com.android.systemui.tuner.TunerService;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5fab796..53fe6ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -380,10 +380,6 @@
                 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
         final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0);
 
-        if (SLIPPERY_WHEN_DISABLED) {
-            setSlippery(disableHome && disableRecent && disableBack && disableSearch);
-        }
-
         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
         if (navButtons != null) {
             LayoutTransition lt = navButtons.getLayoutTransition();
@@ -458,22 +454,6 @@
         }
     }
 
-    public void setSlippery(boolean newSlippery) {
-        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
-        if (lp != null) {
-            boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0;
-            if (!oldSlippery && newSlippery) {
-                lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
-            } else if (oldSlippery && !newSlippery) {
-                lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
-            } else {
-                return;
-            }
-            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
-            wm.updateViewLayout(this, lp);
-        }
-    }
-
     public void setMenuVisibility(final boolean show) {
         setMenuVisibility(show, false);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index ca42ba0f..fde3626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -765,7 +765,7 @@
             // no window manager? good luck with that
         }
 
-        mAssistManager = new AssistManager(this, context);
+        mAssistManager = SystemUIFactory.getInstance().createAssistManager(this, context);
 
         // figure out which pixel-format to use for the status bar.
         mPixelFormat = PixelFormat.OPAQUE;
@@ -1372,7 +1372,8 @@
                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                     | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                    | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
         // this will allow the navbar to run in an overlay on devices that support this
         if (ActivityManager.isHighEndGfx()) {
@@ -2578,8 +2579,6 @@
         }
 
         mExpandedVisible = true;
-        if (mNavigationBarView != null)
-            mNavigationBarView.setSlippery(true);
 
         // Expand the window to encompass the full screen in anticipation of the drag.
         // This is only possible to do atomically because the status bar is at the top of the screen!
@@ -2713,8 +2712,7 @@
         mNotificationPanel.closeQs();
 
         mExpandedVisible = false;
-        if (mNavigationBarView != null)
-            mNavigationBarView.setSlippery(false);
+
         visibilityChanged(false);
 
         // Shrink the window to the size of the status bar only
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 1fd3bdf..0563f4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -221,6 +221,8 @@
             mSimState = IccCardConstants.State.ABSENT;
         } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
             mSimState = IccCardConstants.State.CARD_IO_ERROR;
+        } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED.equals(stateExtra)) {
+            mSimState = IccCardConstants.State.CARD_RESTRICTED;
         } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
             mSimState = IccCardConstants.State.READY;
         } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 68e5d0b..ae1e5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -27,6 +27,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.text.TextUtils;
@@ -320,6 +321,9 @@
             return;
         }
         if (DEBUG) Log.d(TAG, "Recreating tiles");
+        if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
+            newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
+        }
         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
         int currentUser = ActivityManager.getCurrentUser();
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index e091d6dc..40a93df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -23,6 +23,7 @@
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
+import android.os.UserManager;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -82,18 +83,16 @@
     protected MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
 
-    private float mDateTimeTranslation;
-    private float mDateTimeAlarmTranslation;
     private float mDateScaleFactor;
     protected float mGearTranslation;
 
     private TouchAnimator mSecondHalfAnimator;
     private TouchAnimator mFirstHalfAnimator;
     private TouchAnimator mDateSizeAnimator;
-    private TouchAnimator mAlarmTranslation;
     protected TouchAnimator mSettingsAlpha;
     private float mExpansionAmount;
     private QSTileHost mHost;
+    private View mEdit;
     private boolean mShowFullAlarm;
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
@@ -106,6 +105,10 @@
 
         mEmergencyOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
 
+        mEdit = findViewById(android.R.id.edit);
+        findViewById(android.R.id.edit).setOnClickListener(view ->
+                mHost.startRunnableDismissingKeyguard(() -> mQsPanel.showEdit(view)));
+
         mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
         mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
         mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
@@ -154,16 +157,11 @@
 
         mGearTranslation = mContext.getResources().getDimension(R.dimen.qs_header_gear_translation);
 
-        mDateTimeTranslation = mContext.getResources().getDimension(
-                R.dimen.qs_date_anim_translation);
-        mDateTimeAlarmTranslation = mContext.getResources().getDimension(
-                R.dimen.qs_date_alarm_anim_translation);
         float dateCollapsedSize = mContext.getResources().getDimension(
                 R.dimen.qs_date_collapsed_text_size);
         float dateExpandedSize = mContext.getResources().getDimension(
                 R.dimen.qs_date_text_size);
         mDateScaleFactor = dateExpandedSize / dateCollapsedSize;
-        updateDateTimePosition();
 
         mSecondHalfAnimator = new TouchAnimator.Builder()
                 .addFloat(mShowFullAlarm ? mAlarmStatus : findViewById(R.id.date), "alpha", 0, 1)
@@ -187,10 +185,9 @@
 
     protected void updateSettingsAnimator() {
         mSettingsAlpha = new TouchAnimator.Builder()
-                .addFloat(mSettingsContainer, "translationY", -mGearTranslation, 0)
+                .addFloat(mEdit, "translationY", -mGearTranslation, 0)
                 .addFloat(mMultiUserSwitch, "translationY", -mGearTranslation, 0)
-                .addFloat(mSettingsButton, "rotation", -90, 0)
-                .addFloat(mSettingsContainer, "alpha", 0, 1)
+                .addFloat(mEdit, "alpha", 0, 1)
                 .addFloat(mMultiUserSwitch, "alpha", 0, 1)
                 .setStartDelay(QSAnimator.EXPANDED_TILE_DELAY)
                 .build();
@@ -252,7 +249,6 @@
             mFirstHalfAnimator.setPosition(headerExpansionFraction);
         }
         mDateSizeAnimator.setPosition(headerExpansionFraction);
-        mAlarmTranslation.setPosition(headerExpansionFraction);
         mSettingsAlpha.setPosition(headerExpansionFraction);
 
         updateAlarmVisibilities();
@@ -273,15 +269,6 @@
         mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
     }
 
-    private void updateDateTimePosition() {
-        // This one has its own because we have to rebuild it every time the alarm state changes.
-        mAlarmTranslation = new TouchAnimator.Builder()
-                .addFloat(mDateTimeAlarmGroup, "translationY", 0, mAlarmShowing
-                        ? mDateTimeAlarmTranslation : mDateTimeTranslation)
-                .build();
-        mAlarmTranslation.setPosition(mExpansionAmount);
-    }
-
     public void setListening(boolean listening) {
         if (listening == mListening) {
             return;
@@ -293,7 +280,6 @@
 
     @Override
     public void updateEverything() {
-        updateDateTimePosition();
         updateVisibilities();
         setClickable(false);
     }
@@ -302,11 +288,12 @@
         updateAlarmVisibilities();
         mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly
                 ? View.VISIBLE : View.INVISIBLE);
-        mSettingsContainer.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
-        mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers()
+        final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
+        mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
                 ? View.VISIBLE : View.INVISIBLE);
+        mEdit.setVisibility(isDemo ? View.INVISIBLE : View.VISIBLE);
     }
 
     private void updateListeners() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 9c4480e..135c294 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -524,6 +524,10 @@
         mScrimBehind.setExcludedArea(area);
     }
 
+    public void setLeftInset(int inset) {
+        mScrimBehind.setLeftInset(inset);
+    }
+
     public int getScrimBehindColor() {
         return mScrimBehind.getScrimColorWithAlpha();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index ebfa018..7b22b88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -70,6 +70,7 @@
     private View mBrightnessMirror;
 
     private int mRightInset = 0;
+    private int mLeftInset = 0;
 
     private PhoneStatusBar mService;
     private final Paint mTransparentSrcPaint = new Paint();
@@ -93,25 +94,26 @@
     @Override
     protected boolean fitSystemWindows(Rect insets) {
         if (getFitsSystemWindows()) {
-            boolean paddingChanged = insets.left != getPaddingLeft()
-                    || insets.top != getPaddingTop()
+            boolean paddingChanged = insets.top != getPaddingTop()
                     || insets.bottom != getPaddingBottom();
 
             // Super-special right inset handling, because scrims and backdrop need to ignore it.
-            if (insets.right != mRightInset) {
+            if (insets.right != mRightInset || insets.left != mLeftInset) {
                 mRightInset = insets.right;
+                mLeftInset = insets.left;
                 applyMargins();
             }
-            // Drop top inset, apply left inset and pass through bottom inset.
+            // Drop top inset, and pass through bottom inset.
             if (paddingChanged) {
-                setPadding(insets.left, 0, 0, 0);
+                setPadding(0, 0, 0, 0);
             }
             insets.left = 0;
             insets.top = 0;
             insets.right = 0;
         } else {
-            if (mRightInset != 0) {
+            if (mRightInset != 0 || mLeftInset != 0) {
                 mRightInset = 0;
+                mLeftInset = 0;
                 applyMargins();
             }
             boolean changed = getPaddingLeft() != 0
@@ -127,13 +129,16 @@
     }
 
     private void applyMargins() {
+        mService.mScrimController.setLeftInset(mLeftInset);
         final int N = getChildCount();
         for (int i = 0; i < N; i++) {
             View child = getChildAt(i);
             if (child.getLayoutParams() instanceof LayoutParams) {
                 LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (!lp.ignoreRightInset && lp.rightMargin != mRightInset) {
+                if (!lp.ignoreRightInset
+                        && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) {
                     lp.rightMargin = mRightInset;
+                    lp.leftMargin = mLeftInset;
                     child.requestLayout();
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index c8c824a..d8b1a62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -42,11 +44,12 @@
 import android.widget.ImageView;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.ButtonDispatcher;
 
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
-public class KeyButtonView extends ImageView {
+public class KeyButtonView extends ImageView implements ButtonDispatcher.ButtonInterface {
 
     private int mContentDescriptionRes;
     private long mDownTime;
@@ -247,10 +250,21 @@
                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
     }
 
+    @Override
     public void abortCurrentGesture() {
         setPressed(false);
         mGestureAborted = true;
     }
+
+    @Override
+    public void setImageResource(@DrawableRes int resId) {
+        super.setImageResource(resId);
+    }
+
+    @Override
+    public void setImageDrawable(@Nullable Drawable drawable) {
+        super.setImageDrawable(drawable);
+    }
 }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index a633241..b27bfcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -810,7 +810,7 @@
 
     private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
         SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
-                null, 0, 0, "", SubscriptionManager.SIM_PROVISIONED);
+                null, 0, 0, "");
         mMobileSignalControllers.put(id, new MobileSignalController(mContext,
                 mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
                 mSubDefaults, mReceiverHandler.getLooper()));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 7704a07..a2289c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -21,6 +21,7 @@
 import android.app.RemoteInput;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ShortcutManager;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -132,6 +133,15 @@
         mEditText.mShowImeOnInputConnection = false;
         mController.remoteInputSent(mEntry);
 
+        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
+        // will reset the throttling for this package.
+        // Strictly speaking, the intent receiver may be different from the notification publisher,
+        // but that's an edge case, and also because we can't always know which package will receive
+        // an intent, so we just reset for the publisher.
+        getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+                mEntry.notification.getPackageName(),
+                mEntry.notification.getUser().getIdentifier());
+
         MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_SEND,
                 mEntry.notification.getPackageName());
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index a22f988..014afae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -23,6 +23,8 @@
     String getProfileOwnerName();
     boolean isVpnEnabled();
     boolean isVpnRestricted();
+    /** Whether the VPN app should use branded VPN iconography.  */
+    boolean isVpnBranded();
     String getPrimaryVpnName();
     String getProfileVpnName();
     void onUserSwitched(int newUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 5046456..07d3b59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -18,6 +18,8 @@
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
@@ -54,10 +56,13 @@
             .build();
     private static final int NO_NETWORK = -1;
 
+    private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
+
     private final Context mContext;
     private final ConnectivityManager mConnectivityManager;
     private final IConnectivityManager mConnectivityManagerService;
     private final DevicePolicyManager mDevicePolicyManager;
+    private final PackageManager mPackageManager;
     private final UserManager mUserManager;
 
     @GuardedBy("mCallbacks")
@@ -75,6 +80,7 @@
                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
         mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
                 ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+        mPackageManager = context.getPackageManager();
         mUserManager = (UserManager)
                 context.getSystemService(Context.USER_SERVICE);
 
@@ -165,6 +171,21 @@
     }
 
     @Override
+    public boolean isVpnBranded() {
+        VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
+        if (cfg == null) {
+            return false;
+        }
+
+        String packageName = getPackageNameForVpnConfig(cfg);
+        if (packageName == null) {
+            return false;
+        }
+
+        return isVpnPackageBranded(packageName);
+    }
+
+    @Override
     public void removeCallback(SecurityControllerCallback callback) {
         synchronized (mCallbacks) {
             if (callback == null) return;
@@ -245,6 +266,28 @@
         mCurrentVpns = vpns;
     }
 
+    private String getPackageNameForVpnConfig(VpnConfig cfg) {
+        if (cfg.legacy) {
+            return null;
+        }
+        return cfg.user;
+    }
+
+    private boolean isVpnPackageBranded(String packageName) {
+        boolean isBranded;
+        try {
+            ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
+                PackageManager.GET_META_DATA);
+            if (info == null || info.metaData == null || !info.isSystemApp()) {
+                return false;
+            }
+            isBranded = info.metaData.getBoolean(VPN_BRANDED_META_DATA, false);
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+        return isBranded;
+    }
+
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
         @Override
         public void onAvailable(Network network) {
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index a1487e3..5527a41 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2212,12 +2212,118 @@
     // Notification group expansion state toggled by the expand affordance.
     ACTION_NOTIFICATION_GROUP_EXPANDER = 408;
 
+
     // Notification expansion state toggled by the expand gesture.
     ACTION_NOTIFICATION_GESTURE_EXPANDER = 409;
 
     // Notification group expansion state toggled by the expand gesture.
     ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER = 410;
 
+    // User performs gesture that activates the ambient display
+    // 1: Gesture performed is Nudge
+    // 2: Gesture performed is Pickup
+    // 4: Gesture performed is Double Tap
+    ACTION_AMBIENT_GESTURE = 411;
+
+    // ---- End N Constants, all N constants go above this line ----
+
+    // ------- Begin N App Disambig Shade -----
+    // Application disambig shade opened or closed with a featured app.
+    // These are actually visibility events, but visible/hidden doesn't
+    // take a package, so these are being logged as actions.
+    // Package: Calling app on open, called app on close
+    ACTION_SHOW_APP_DISAMBIG_APP_FEATURED = 451;
+    ACTION_HIDE_APP_DISAMBIG_APP_FEATURED = 452;
+
+    // Application disambig shade opened or closed without a featured app.
+    // These are actually visibility events, but visible/hidden doesn't
+    // take a package, so these are being logged as actions.
+    // Package: Calling app on open, called app on close
+    ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED = 453;
+    ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED = 454;
+
+    // User opens in an app by pressing “Always” in the application disambig shade.
+    // Subtype: Index of selection
+    ACTION_APP_DISAMBIG_ALWAYS = 455;
+
+    // User opens in an app by pressing “Just Once” in the application disambig shade.
+    // Subtype: Index of selection
+    ACTION_APP_DISAMBIG_JUST_ONCE = 456;
+
+    // User opens in an app by tapping on its name in the application disambig shade.
+    // Subtype: Index of selection
+    ACTION_APP_DISAMBIG_TAP = 457;
+
+    // OPEN: Settings > Internal storage > Storage manager
+    // CATEGORY: SETTINGS
+    STORAGE_MANAGER_SETTINGS = 458;
+
+    // OPEN: Settings -> Gestures
+    // CATEGORY: SETTINGS
+    SETTINGS_GESTURES = 459;
+
+    // ------ Begin Deletion Helper ------
+    // ACTION: Settings > Storage > Free Up Space > Photos & Videos > Toggle
+    //   SUBTYPE: false is off, true is on
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_SELECTION_PHOTOS = 460;
+
+    // ACTION: Settings > Storage > Free Up Space > Apps > Toggle
+    //   SUBTYPE: false is off, true is on
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_SELECTION_ALL_APPS = 461;
+
+    // ACTION: Settings > Storage > Free Up Space > Apps > Click an unchecked app
+    // CATEGORY: SETTINGS
+    //   PACKAGE: Unchecked app
+    ACTION_DELETION_SELECTION_APP_ON = 462;
+
+    // ACTION: Settings > Storage > Free Up Space > Apps > Click a checked app
+    // CATEGORY: SETTINGS
+    //   PACKAGE: Checked app
+    ACTION_DELETION_SELECTION_APP_OFF = 463;
+
+    // ACTION: Settings > Storage > Free Up Space > Apps > Click category
+    //   SUBTYPE: false is expanded, true is collapsed
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_APPS_COLLAPSED = 464;
+
+    // ACTION: Settings > Storage > Free Up Space > Downloads > Check On
+    //   SUBTYPE: false is off, true is on
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_SELECTION_DOWNLOADS = 465;
+
+    // ACTION: Settings > Storage > Free Up Space > Downloads > Click category
+    //   SUBTYPE: false is expanded, true is collapsed
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_DOWNLOADS_COLLAPSED = 466;
+
+    // ACTION: Settings > Storage > Free Up Space > Free up ... GB
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_HELPER_CLEAR = 467;
+
+    // ACTION: Settings > Storage > Free Up Space > Cancel
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_HELPER_CANCEL = 468;
+
+    // ACTION: Settings > Storage > Free Up Space > Free up ... GB > Remove
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_HELPER_REMOVE_CONFIRM = 469;
+
+    // ACTION: Settings > Storage > Free Up Space > Free up ... GB > Cancel
+    // CATEGORY: SETTINGS
+    ACTION_DELETION_HELPER_REMOVE_CANCEL = 470;
+
+    // Deletion helper encountered an error during package deletion.
+    ACTION_DELETION_HELPER_APPS_DELETION_FAIL = 471;
+
+    // Deletion helper encountered an error during downloads folder deletion.
+    ACTION_DELETION_HELPER_DOWNLOADS_DELETION_FAIL = 472;
+
+    // Deletion helper encountered an error during photo and video deletion.
+    ACTION_DELETION_HELPER_PHOTOS_VIDEOS_DELETION_FAIL = 473;
+
+    // ---- End N-MR1 Constants, all N-MR1 constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 4e0ddd6..04a0990 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -29,6 +29,7 @@
 import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.PendingHostUpdate;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -70,10 +71,12 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.AttributeSet;
+import android.util.LongSparseArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.SparseLongArray;
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.Display;
@@ -732,8 +735,8 @@
     }
 
     @Override
-    public ParceledListSlice<RemoteViews> startListening(IAppWidgetHost callbacks,
-            String callingPackage, int hostId, int[] appWidgetIds, int[] updatedIds) {
+    public ParceledListSlice<PendingHostUpdate> startListening(IAppWidgetHost callbacks,
+            String callingPackage, int hostId, int[] appWidgetIds) {
         final int userId = UserHandle.getCallingUserId();
 
         if (DEBUG) {
@@ -753,18 +756,19 @@
             host.callbacks = callbacks;
 
             int N = appWidgetIds.length;
-            ArrayList<RemoteViews> outViews = new ArrayList<>(N);
-            RemoteViews rv;
-            int added = 0;
+            ArrayList<PendingHostUpdate> outUpdates = new ArrayList<>(N);
+
+            LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
             for (int i = 0; i < N; i++) {
-                rv = host.getPendingViewsForId(appWidgetIds[i]);
-                if (rv != null) {
-                    updatedIds[added] = appWidgetIds[i];
-                    outViews.add(rv);
-                    added++;
+                if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
+                    // We key the updates based on time, so that the values are sorted by time.
+                    int M = updatesMap.size();
+                    for (int j = 0; j < M; j++) {
+                        outUpdates.add(updatesMap.valueAt(j));
+                    }
                 }
             }
-            return new ParceledListSlice<>(outViews);
+            return new ParceledListSlice<>(outUpdates);
         }
     }
 
@@ -1804,6 +1808,15 @@
     }
 
     private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) {
+        if (viewId == ID_VIEWS_UPDATE || viewId == ID_PROVIDER_CHANGED) {
+            // A view id should never collide with these constants but a developer can call this
+            // method with a wrong id. In that case, ignore the call.
+            return;
+        }
+        long requestTime = SystemClock.uptimeMillis();
+        if (widget != null) {
+            widget.updateTimes.put(viewId, requestTime);
+        }
         if (widget == null || widget.host == null || widget.host.zombie
                 || widget.host.callbacks == null || widget.provider == null
                 || widget.provider.zombie) {
@@ -1813,6 +1826,7 @@
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = widget.host;
         args.arg2 = widget.host.callbacks;
+        args.arg3 = requestTime;
         args.argi1 = widget.appWidgetId;
         args.argi2 = viewId;
 
@@ -1823,9 +1837,10 @@
 
 
     private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
-            int appWidgetId, int viewId) {
+            int appWidgetId, int viewId, long requestTime) {
         try {
             callbacks.viewDataChanged(appWidgetId, viewId);
+            host.lastWidgetUpdateTime = requestTime;
         } catch (RemoteException re) {
             // It failed; remove the callback. No need to prune because
             // we know that this host is still referenced by this instance.
@@ -1874,7 +1889,7 @@
     private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
         long requestTime = SystemClock.uptimeMillis();
         if (widget != null) {
-            widget.lastUpdateTime = requestTime;
+            widget.updateTimes.put(ID_VIEWS_UPDATE, requestTime);
         }
         if (widget == null || widget.provider == null || widget.provider.zombie
                 || widget.host.callbacks == null || widget.host.zombie) {
@@ -1907,6 +1922,12 @@
     }
 
     private void scheduleNotifyProviderChangedLocked(Widget widget) {
+        long requestTime = SystemClock.uptimeMillis();
+        if (widget != null) {
+            // When the provider changes, reset everything else.
+            widget.updateTimes.clear();
+            widget.updateTimes.append(ID_PROVIDER_CHANGED, requestTime);
+        }
         if (widget == null || widget.provider == null || widget.provider.zombie
                 || widget.host.callbacks == null || widget.host.zombie) {
             return;
@@ -1916,6 +1937,7 @@
         args.arg1 = widget.host;
         args.arg2 = widget.host.callbacks;
         args.arg3 = widget.provider.info;
+        args.arg4 = requestTime;
         args.argi1 = widget.appWidgetId;
 
         mCallbackHandler.obtainMessage(
@@ -1924,9 +1946,10 @@
     }
 
     private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
-            int appWidgetId, AppWidgetProviderInfo info) {
+            int appWidgetId, AppWidgetProviderInfo info, long requestTime) {
         try {
             callbacks.providerChanged(appWidgetId, info);
+            host.lastWidgetUpdateTime = requestTime;
         } catch (RemoteException re) {
             synchronized (mLock){
                 Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -3410,10 +3433,11 @@
                     Host host = (Host) args.arg1;
                     IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
                     AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3;
+                    long requestTime = (Long) args.arg4;
                     final int appWidgetId = args.argi1;
                     args.recycle();
 
-                    handleNotifyProviderChanged(host, callbacks, appWidgetId, info);
+                    handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestTime);
                 } break;
 
                 case MSG_NOTIFY_PROVIDERS_CHANGED: {
@@ -3429,11 +3453,13 @@
                     SomeArgs args = (SomeArgs) message.obj;
                     Host host = (Host) args.arg1;
                     IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+                    long requestTime = (Long) args.arg3;
                     final int appWidgetId = args.argi1;
                     final int viewId = args.argi2;
                     args.recycle();
 
-                    handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId);
+                    handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId,
+                            requestTime);
                 } break;
             }
         }
@@ -3772,20 +3798,41 @@
         }
 
         /**
-         * Returns the RemoveViews for the provided widget id if an update is pending
-         * for that widget.
+         * Adds all pending updates in {@param outUpdates} keys by the update time.
          */
-        public RemoteViews getPendingViewsForId(int appWidgetId) {
+        public boolean getPendingUpdatesForId(int appWidgetId,
+                LongSparseArray<PendingHostUpdate> outUpdates) {
             long updateTime = lastWidgetUpdateTime;
             int N = widgets.size();
             for (int i = 0; i < N; i++) {
                 Widget widget = widgets.get(i);
-                if (widget.appWidgetId == appWidgetId
-                        && widget.lastUpdateTime > updateTime) {
-                    return cloneIfLocalBinder(widget.getEffectiveViewsLocked());
+                if (widget.appWidgetId == appWidgetId) {
+                    outUpdates.clear();
+                    for (int j = widget.updateTimes.size() - 1; j >= 0; j--) {
+                        long time = widget.updateTimes.valueAt(j);
+                        if (time <= updateTime) {
+                            continue;
+                        }
+                        int id = widget.updateTimes.keyAt(j);
+                        final PendingHostUpdate update;
+                        switch (id) {
+                            case ID_PROVIDER_CHANGED:
+                                update = PendingHostUpdate.providerChanged(
+                                        appWidgetId, widget.provider.info);
+                                break;
+                            case ID_VIEWS_UPDATE:
+                                update = PendingHostUpdate.updateAppWidget(appWidgetId,
+                                        cloneIfLocalBinder(widget.getEffectiveViewsLocked()));
+                                break;
+                            default:
+                                update = PendingHostUpdate.viewDataChanged(appWidgetId, id);
+                        }
+                        outUpdates.put(time, update);
+                    }
+                    return true;
                 }
             }
-            return null;
+            return false;
         }
 
         @Override
@@ -3850,6 +3897,10 @@
         }
     }
 
+    // These can be any constants that would not collide with a resource id.
+    private static final int ID_VIEWS_UPDATE = 0;
+    private static final int ID_PROVIDER_CHANGED = 1;
+
     private static final class Widget {
         int appWidgetId;
         int restoredId;  // tracking & remapping any restored state
@@ -3858,7 +3909,8 @@
         RemoteViews maskedViews;
         Bundle options;
         Host host;
-        long lastUpdateTime;
+        // timestamps for various operations
+        SparseLongArray updateTimes = new SparseLongArray(2);
 
         @Override
         public String toString() {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 9154b8e..71964d1 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -425,6 +425,24 @@
         return false;
     }
 
+    public int getState() {
+        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+                (!checkIfCallerIsForegroundUser())) {
+            Slog.w(TAG, "getState(): not allowed for non-active and non system user");
+            return BluetoothAdapter.STATE_OFF;
+        }
+
+        try {
+            mBluetoothLock.readLock().lock();
+            if (mBluetooth != null) return mBluetooth.getState();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "getState()", e);
+        } finally {
+            mBluetoothLock.readLock().unlock();
+        }
+        return BluetoothAdapter.STATE_OFF;
+    }
+
     class ClientDeathRecipient implements IBinder.DeathRecipient {
         public void binderDied() {
             if (DBG) Slog.d(TAG, "Binder is dead -  unregister Ble App");
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 130fb7c..e65b50c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -797,6 +797,17 @@
         return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
     }
 
+    // Used only for testing.
+    // TODO: Delete this and either:
+    // 1. Give Fake SettingsProvider the ability to send settings change notifications (requires
+    //    changing ContentResolver to make registerContentObserver non-final).
+    // 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it
+    //    by subclassing SettingsObserver.
+    @VisibleForTesting
+    void updateMobileDataAlwaysOn() {
+        mHandler.sendEmptyMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
+    }
+
     private void handleMobileDataAlwaysOn() {
         final boolean enable = (Settings.Global.getInt(
                 mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 0) == 1);
@@ -2161,9 +2172,11 @@
                             loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
                             break;
                         }
-                        setProvNotificationVisibleIntent(true, netId, NotificationType.SIGN_IN,
-                                nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(),
-                                (PendingIntent)msg.obj, nai.networkMisc.explicitlySelected);
+                        if (!nai.networkMisc.provisioningNotificationDisabled) {
+                            setProvNotificationVisibleIntent(true, netId, NotificationType.SIGN_IN,
+                                    nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(),
+                                    (PendingIntent)msg.obj, nai.networkMisc.explicitlySelected);
+                        }
                     }
                     break;
                 }
@@ -2564,6 +2577,7 @@
 
         PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
                 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+
         setProvNotificationVisibleIntent(true, nai.network.netId, NotificationType.NO_INTERNET,
                 nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(), pendingIntent, true);
     }
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 1653db9..6f8edec 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -47,6 +47,7 @@
         final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
         final boolean wipeExternalStorage = intent.getBooleanExtra(
                 Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
+        final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false);
 
         Slog.w(TAG, "!!! FACTORY RESET !!!");
         // The reboot call is blocking, so we need to do it on another thread.
@@ -54,7 +55,7 @@
             @Override
             public void run() {
                 try {
-                    RecoverySystem.rebootWipeUserData(context, shutdown, reason);
+                    RecoverySystem.rebootWipeUserData(context, shutdown, reason, forceWipe);
                     Log.wtf(TAG, "Still running after master clear?!");
                 } catch (IOException e) {
                     Slog.e(TAG, "Can't perform master clear/factory reset", e);
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 680547a..e233b1c 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -26,6 +26,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Slog;
@@ -155,6 +156,15 @@
                     "Only the Admin user is allowed to change OEM unlock state");
         }
     }
+
+    private void enforceFactoryResetAllowed() {
+        final boolean isOemUnlockRestricted = UserManager.get(mContext)
+                .hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET);
+        if (isOemUnlockRestricted) {
+            throw new SecurityException("OEM unlock is disallowed by DISALLOW_FACTORY_RESET");
+        }
+    }
+
     private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
         // skip over checksum
         inputStream.skipBytes(DIGEST_SIZE_BYTES);
@@ -447,14 +457,24 @@
         }
 
         @Override
-        public void setOemUnlockEnabled(boolean enabled) {
+        public void setOemUnlockEnabled(boolean enabled) throws SecurityException {
             // do not allow monkey to flip the flag
             if (ActivityManager.isUserAMonkey()) {
                 return;
             }
+
             enforceOemUnlockWritePermission();
             enforceIsAdmin();
 
+            if (enabled) {
+                // Do not allow oem unlock to be enabled if it has been disallowed.
+                if (Settings.Global.getInt(getContext().getContentResolver(),
+                        Settings.Global.OEM_UNLOCK_DISALLOWED, 0) == 1) {
+                    throw new SecurityException(
+                            "OEM unlock has been disallowed by OEM_UNLOCK_DISALLOWED.");
+                }
+                enforceFactoryResetAllowed();
+            }
             synchronized (mLock) {
                 doSetOemUnlockEnabledLocked(enabled);
                 computeAndWriteDigestLocked();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 09f24ed..2c7ef7e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18730,8 +18730,9 @@
 
     /**
      * Decide based on the configuration whether we should shouw the ANR,
-     * crash, etc dialogs.  The idea is that if there is no affordnace to
-     * press the on-screen buttons, we shouldn't show the dialog.
+     * crash, etc dialogs.  The idea is that if there is no affordence to
+     * press the on-screen buttons, or the user experience would be more
+     * greatly impacted than the crash itself, we shouldn't show the dialog.
      *
      * A thought: SystemUI might also want to get told about this, the Power
      * dialog / global actions also might want different behaviors.
@@ -18740,9 +18741,10 @@
         final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
                                    && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
                                    && config.navigation == Configuration.NAVIGATION_NONAV);
-        final boolean uiIsNotCarType = !((config.uiMode & Configuration.UI_MODE_TYPE_MASK)
-                                    == Configuration.UI_MODE_TYPE_CAR);
-        return inputMethodExists && uiIsNotCarType && !inVrMode;
+        int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
+        final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
+                && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE)));
+        return inputMethodExists && uiModeSupportsDialogs && !inVrMode;
     }
 
     @Override
@@ -21207,6 +21209,11 @@
                 Slog.w(TAG, "No user info for user #" + targetUserId);
                 return false;
             }
+            if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mContext)) {
+                Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
+                        + " when device is in demo mode");
+                return false;
+            }
             if (!targetUserInfo.supportsSwitchTo()) {
                 Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
                 return false;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 7a43d53..f78fa8d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -870,6 +870,8 @@
                 }
             }
         }
+        // Send launch end powerhint when idle
+        mService.mActivityStarter.sendPowerHintForLaunchEndIfNeeded();
         return true;
     }
 
@@ -2741,6 +2743,9 @@
             }
         }
 
+        // Send launch end powerhint before going sleep
+        mService.mActivityStarter.sendPowerHintForLaunchEndIfNeeded();
+
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -4408,6 +4413,7 @@
         // Work Challenge is present) let startActivityInPackage handle the intercepting.
         if (!mService.mUserController.shouldConfirmCredentials(task.userId)
                 && task.getRootActivity() != null) {
+            mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */);
             mActivityMetricsLogger.notifyActivityLaunching();
             mService.moveTaskToFrontLocked(task.taskId, 0, bOptions);
             mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 522e42b..793f411 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -102,6 +102,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.PowerManagerInternal;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -173,6 +174,7 @@
     private boolean mNoAnimation;
     private boolean mKeepCurTransition;
     private boolean mAvoidMoveToFront;
+    private boolean mPowerHintSent;
 
     private IVoiceInteractionSession mVoiceSession;
     private IVoiceInteractor mVoiceInteractor;
@@ -944,6 +946,28 @@
         return START_SUCCESS;
     }
 
+    void sendPowerHintForLaunchStartIfNeeded(boolean forceSend) {
+        // Trigger launch power hint if activity being launched is not in the current task
+        final ActivityStack focusStack = mSupervisor.getFocusedStack();
+        final ActivityRecord curTop = (focusStack == null)
+            ? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);
+        if ((forceSend || (!mPowerHintSent && curTop != null &&
+                curTop.task != null && mStartActivity != null &&
+                curTop.task != mStartActivity.task )) &&
+                mService.mLocalPowerManager != null) {
+            mService.mLocalPowerManager.powerHint(PowerManagerInternal.POWER_HINT_LAUNCH, 1);
+            mPowerHintSent = true;
+        }
+    }
+
+    void sendPowerHintForLaunchEndIfNeeded() {
+        // Trigger launch power hint if activity is launched
+        if (mPowerHintSent && mService.mLocalPowerManager != null) {
+            mService.mLocalPowerManager.powerHint(PowerManagerInternal.POWER_HINT_LAUNCH, 0);
+            mPowerHintSent = false;
+        }
+    }
+
     private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
@@ -1005,6 +1029,8 @@
                 }
             }
 
+            sendPowerHintForLaunchStartIfNeeded(false /* forceSend */);
+
             mReusedActivity = setTargetStackAndMoveToFrontIfNeeded(mReusedActivity);
 
             if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
@@ -1126,6 +1152,9 @@
         ActivityStack.logStartActivity(
                 EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.task);
         mTargetStack.mLastPausedActivity = null;
+
+        sendPowerHintForLaunchStartIfNeeded(false /* forceSend */);
+
         mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);
         if (mDoResume) {
             if (!mLaunchTaskBehind) {
diff --git a/services/core/java/com/android/server/am/RetailDemoModeService.java b/services/core/java/com/android/server/am/RetailDemoModeService.java
new file mode 100644
index 0000000..6a5ec96
--- /dev/null
+++ b/services/core/java/com/android/server/am/RetailDemoModeService.java
@@ -0,0 +1,318 @@
+/*
+ * 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.am;
+
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+import java.io.File;
+
+public class RetailDemoModeService extends SystemService {
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = RetailDemoModeService.class.getSimpleName();
+    private static final String DEMO_USER_NAME = "Demo";
+    private static final String ACTION_RESET_DEMO = "com.android.server.am.ACTION_RESET_DEMO";
+
+    private static final int MSG_TURN_SCREEN_ON = 0;
+    private static final int MSG_INACTIVITY_TIME_OUT = 1;
+    private static final int MSG_START_NEW_SESSION = 2;
+
+    private static final long SCREEN_WAKEUP_DELAY = 2500;
+    private static final long USER_INACTIVITY_TIMEOUT = 30000;
+
+    boolean mDeviceInDemoMode = false;
+    private ActivityManagerService mAms;
+    private NotificationManager mNm;
+    private UserManager mUm;
+    private PowerManager mPm;
+    private PowerManager.WakeLock mWakeLock;
+    Handler mHandler;
+    private ServiceThread mHandlerThread;
+    private PendingIntent mResetDemoPendingIntent;
+
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!mDeviceInDemoMode) {
+                return;
+            }
+            switch (intent.getAction()) {
+                case Intent.ACTION_SCREEN_OFF:
+                    mHandler.removeMessages(MSG_TURN_SCREEN_ON);
+                    mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
+                    break;
+                case ACTION_RESET_DEMO:
+                    mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+                    break;
+            }
+        }
+    };
+
+    final class MainHandler extends Handler {
+
+        MainHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_TURN_SCREEN_ON:
+                    if (mWakeLock.isHeld()) {
+                        mWakeLock.release();
+                    }
+                    mWakeLock.acquire();
+                    break;
+                case MSG_INACTIVITY_TIME_OUT:
+                    IPackageManager pm = AppGlobals.getPackageManager();
+                    int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+                    String demoLauncherComponent = getContext().getResources()
+                            .getString(R.string.config_demoModeLauncherComponent);
+                    try {
+                        enabledState = pm.getComponentEnabledSetting(
+                                ComponentName.unflattenFromString(demoLauncherComponent),
+                            getActivityManager().getCurrentUser().id);
+                    } catch (RemoteException exc) {
+                        // XXX: shouldn't happen
+                    }
+                    if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
+                        Slog.i(TAG, "Restarting session due to user inactivity timeout");
+                        sendEmptyMessage(MSG_START_NEW_SESSION);
+                    }
+                    break;
+                case MSG_START_NEW_SESSION:
+                    if (DEBUG) {
+                        Slog.d(TAG, "Switching to a new demo user");
+                    }
+                    removeMessages(MSG_START_NEW_SESSION);
+                    UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
+                            UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
+                    if (demoUser != null) {
+                        setupDemoUser(demoUser);
+                        getActivityManager().switchUser(demoUser.id);
+                    }
+                    break;
+            }
+        }
+    }
+
+    public RetailDemoModeService(Context context) {
+        super(context);
+    }
+
+    private Notification createResetNotification() {
+        return new Notification.Builder(getContext())
+                .setContentTitle(getContext().getString(R.string.reset_retail_demo_mode_title))
+                .setContentText(getContext().getString(R.string.reset_retail_demo_mode_text))
+                .setOngoing(true)
+                .setSmallIcon(R.drawable.platlogo)
+                .setShowWhen(false)
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setContentIntent(getResetDemoPendingIntent())
+                .setColor(getContext().getColor(R.color.system_notification_accent_color))
+                .build();
+    }
+
+    private PendingIntent getResetDemoPendingIntent() {
+        if (mResetDemoPendingIntent == null) {
+            Intent intent = new Intent(ACTION_RESET_DEMO);
+            mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+        }
+        return mResetDemoPendingIntent;
+    }
+
+    void setupDemoUser(UserInfo userInfo) {
+        UserManager um = getUserManager();
+        UserHandle user = UserHandle.of(userInfo.id);
+        LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext());
+        lockPatternUtils.setLockScreenDisabled(true, userInfo.id);
+        um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
+        um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
+        um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
+        um.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user);
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
+    }
+
+    private ActivityManagerService getActivityManager() {
+        if (mAms == null) {
+            mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
+        }
+        return mAms;
+    }
+
+    private UserManager getUserManager() {
+        if (mUm == null) {
+            mUm = getContext().getSystemService(UserManager.class);
+        }
+        return mUm;
+    }
+
+    private void registerSettingsChangeObserver() {
+        final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
+        final Uri deviceProvisionedUri = Settings.Global.getUriFor(
+                Settings.Global.DEVICE_PROVISIONED);
+        final ContentResolver cr = getContext().getContentResolver();
+        final ContentObserver deviceDemoModeSettingObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri, int userId) {
+                if (deviceDemoModeUri.equals(uri)) {
+                    mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
+                    if (mDeviceInDemoMode) {
+                        mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+                    } else if (mWakeLock.isHeld()) {
+                        mWakeLock.release();
+                    }
+                }
+                // If device is provisioned and left demo mode - run the cleanup in demo folder
+                if (!mDeviceInDemoMode && isDeviceProvisioned()) {
+                    // Run on the bg thread to not block the fg thread
+                    BackgroundThread.getHandler().post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!deleteDemoFolderContents()) {
+                                Slog.w(TAG, "Failed to delete demo folder contents");
+                            }
+                        }
+                    });
+                }
+            }
+        };
+        cr.registerContentObserver(deviceDemoModeUri, false, deviceDemoModeSettingObserver,
+                UserHandle.USER_SYSTEM);
+        cr.registerContentObserver(deviceProvisionedUri, false, deviceDemoModeSettingObserver,
+                UserHandle.USER_SYSTEM);
+    }
+
+    boolean isDeviceProvisioned() {
+        return Settings.Global.getInt(
+                getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+    }
+
+    private boolean deleteDemoFolderContents() {
+        File dir = Environment.getDataPreloadsDemoDirectory();
+        Slog.i(TAG, "Deleting contents of " + dir);
+        return FileUtils.deleteContents(dir);
+    }
+
+    private void registerBroadcastReceiver() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(ACTION_RESET_DEMO);
+        getContext().registerReceiver(mBroadcastReceiver, filter);
+    }
+
+    @Override
+    public void onStart() {
+        if (DEBUG) {
+            Slog.d(TAG, "Service starting up");
+        }
+        mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
+                false);
+        mHandlerThread.start();
+        mHandler = new MainHandler(mHandlerThread.getLooper());
+        publishLocalService(RetailDemoModeServiceInternal.class, mLocalService);
+    }
+
+    @Override
+    public void onBootPhase(int bootPhase) {
+        if (bootPhase != PHASE_THIRD_PARTY_APPS_CAN_START) {
+            return;
+        }
+        mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+        mWakeLock = mPm
+                .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+        mNm = NotificationManager.from(getContext());
+
+        if (UserManager.isDeviceInDemoMode(getContext())) {
+            mDeviceInDemoMode = true;
+            mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+        }
+        registerSettingsChangeObserver();
+        registerBroadcastReceiver();
+    }
+
+    @Override
+    public void onSwitchUser(int userId) {
+        if (!mDeviceInDemoMode) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "onSwitchUser: " + userId);
+        }
+        UserInfo ui = getUserManager().getUserInfo(userId);
+        if (!ui.isDemo()) {
+            Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
+            return;
+        }
+        if (!mWakeLock.isHeld()) {
+            mWakeLock.acquire();
+        }
+        mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
+    }
+
+    public RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
+        private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
+        private long mLastUserActivityTime = 0;
+
+        @Override
+        public void onUserActivity() {
+            if (!mDeviceInDemoMode) {
+                return;
+            }
+            long timeOfActivity = SystemClock.uptimeMillis();
+            if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
+                return;
+            }
+            mLastUserActivityTime = timeOfActivity;
+            mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
+            mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, USER_INACTIVITY_TIMEOUT);
+        }
+    };
+}
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl b/services/core/java/com/android/server/am/RetailDemoModeServiceInternal.java
similarity index 64%
copy from telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
copy to services/core/java/com/android/server/am/RetailDemoModeServiceInternal.java
index b7e78d1..32de03a 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
+++ b/services/core/java/com/android/server/am/RetailDemoModeServiceInternal.java
@@ -1,22 +1,21 @@
 /*
- * Copyright 2016, The Android Open Source Project
+ * 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
+ *      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.
+ * limitations under the License
  */
 
-package android.telecom;
+package com.android.server.am;
 
-/**
- * {@hide}
- */
-parcelable ParcelableCallAnalytics;
+public interface RetailDemoModeServiceInternal {
+    public void onUserActivity();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 4380af3..2375d7a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -224,6 +224,7 @@
 
     private void finishUserBoot(UserState uss, IIntentReceiver resultTo) {
         final int userId = uss.mHandle.getIdentifier();
+
         Slog.d(TAG, "Finishing user boot " + userId);
         synchronized (mService) {
             // Bail if we ended up with a stale user
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 10e88e6..7022856 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -68,6 +68,12 @@
         String viewMessage;
         if (UserManager.isSplitSystemUser() && newUser.id == UserHandle.USER_SYSTEM) {
             viewMessage = res.getString(R.string.user_logging_out_message, oldUser.name);
+        } else if (UserManager.isDeviceInDemoMode(context)) {
+            if (oldUser.isDemo()) {
+                viewMessage = res.getString(R.string.demo_restarting_message);
+            } else {
+                viewMessage = res.getString(R.string.demo_starting_message);
+            }
         } else {
             viewMessage = res.getString(R.string.user_switching_message, newUser.name);
         }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index f4e1424..ddaebfa 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -66,7 +66,6 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
-import com.android.server.connectivity.NetworkAgentInfo;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -618,7 +617,7 @@
 
         @Override
         public void exit() {
-             removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
+            removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 1012f9a..bef48d6 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -34,8 +34,6 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -58,18 +56,20 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.IoThread;
+import com.android.server.connectivity.tethering.IControlsTethering;
+import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
 import com.android.server.net.BaseNetworkObserver;
 
 import java.io.FileDescriptor;
@@ -81,18 +81,16 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 
 /**
  * @hide
  *
- * Timeout
- *
- * TODO - look for parent classes and code sharing
+ * This class holds much of the business logic to allow Android devices
+ * to act as IP gateways via USB, BT, and WiFi interfaces.
  */
-public class Tethering extends BaseNetworkObserver {
+public class Tethering extends BaseNetworkObserver implements IControlsTethering {
 
     private final Context mContext;
     private final static String TAG = "Tethering";
@@ -100,7 +98,7 @@
     private final static boolean VDBG = false;
 
     private static final Class[] messageClasses = {
-            Tethering.class, TetherMasterSM.class, TetherInterfaceSM.class
+            Tethering.class, TetherMasterSM.class, TetherInterfaceStateMachine.class
     };
     private static final SparseArray<String> sMagicDecoderRing =
             MessageUtils.findMessageNames(messageClasses);
@@ -126,17 +124,25 @@
     private final INetworkStatsService mStatsService;
     private final Looper mLooper;
 
-    private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
+    private static class TetherState {
+        public final TetherInterfaceStateMachine mStateMachine;
+        public int mLastState;
+        public int mLastError;
+        public TetherState(TetherInterfaceStateMachine sm) {
+            mStateMachine = sm;
+            // Assume all state machines start out available and with no errors.
+            mLastState = IControlsTethering.STATE_AVAILABLE;
+            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+        }
+    }
+    private final ArrayMap<String, TetherState> mTetherStates;
 
-    private BroadcastReceiver mStateReceiver;
+    private final BroadcastReceiver mStateReceiver;
 
     // {@link ComponentName} of the Service used to run tether provisioning.
     private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
             .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
 
-    private static final String USB_NEAR_IFACE_ADDR      = "192.168.42.129";
-    private static final int USB_PREFIX_LENGTH        = 24;
-
     // USB is  192.168.42.1 and 255.255.255.0
     // Wifi is 192.168.43.1 and 255.255.255.0
     // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
@@ -166,6 +172,9 @@
     private boolean mUsbTetherRequested; // true if USB tethering should be started
                                          // when RNDIS is enabled
 
+    // True iff WiFi tethering should be started when soft AP is ready.
+    private boolean mWifiTetherRequested;
+
     public Tethering(Context context, INetworkManagementService nmService,
             INetworkStatsService statsService) {
         mContext = context;
@@ -174,7 +183,7 @@
 
         mPublicSync = new Object();
 
-        mIfaces = new HashMap<String, TetherInterfaceSM>();
+        mTetherStates = new ArrayMap<>();
 
         // make our own thread so we don't anr the system
         mLooper = IoThread.get().getLooper();
@@ -187,6 +196,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         mContext.registerReceiver(mStateReceiver, filter);
 
@@ -227,7 +237,7 @@
 
         int ifaceTypes[] = mContext.getResources().getIntArray(
                 com.android.internal.R.array.config_tether_upstream_types);
-        Collection<Integer> upstreamIfaceTypes = new ArrayList();
+        Collection<Integer> upstreamIfaceTypes = new ArrayList<>();
         for (int i : ifaceTypes) {
             upstreamIfaceTypes.add(new Integer(i));
         }
@@ -248,34 +258,26 @@
         // Never called directly: only called from interfaceLinkStateChanged.
         // See NetlinkHandler.cpp:71.
         if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
-        boolean found = false;
-        boolean usb = false;
         synchronized (mPublicSync) {
-            if (isWifi(iface)) {
-                found = true;
-            } else if (isUsb(iface)) {
-                found = true;
-                usb = true;
-            } else if (isBluetooth(iface)) {
-                found = true;
+            int interfaceType = ifaceNameToType(iface);
+            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
+                return;
             }
-            if (found == false) return;
 
-            TetherInterfaceSM sm = mIfaces.get(iface);
+            TetherState tetherState = mTetherStates.get(iface);
             if (up) {
-                if (sm == null) {
-                    sm = new TetherInterfaceSM(iface, mLooper, usb);
-                    mIfaces.put(iface, sm);
-                    sm.start();
+                if (tetherState == null) {
+                    trackNewTetherableInterface(iface, interfaceType);
                 }
             } else {
-                if (isUsb(iface)) {
+                if (interfaceType == ConnectivityManager.TETHERING_USB) {
                     // ignore usb0 down after enabling RNDIS
                     // we will handle disconnect in interfaceRemoved instead
                     if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
-                } else if (sm != null) {
-                    sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
-                    mIfaces.remove(iface);
+                } else if (tetherState != null) {
+                    tetherState.mStateMachine.sendMessage(
+                            TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+                    mTetherStates.remove(iface);
                 }
             }
         }
@@ -295,7 +297,7 @@
         }
     }
 
-    public boolean isWifi(String iface) {
+    private boolean isWifi(String iface) {
         synchronized (mPublicSync) {
             for (String regex : mTetherableWifiRegexs) {
                 if (iface.matches(regex)) return true;
@@ -304,7 +306,7 @@
         }
     }
 
-    public boolean isBluetooth(String iface) {
+    private boolean isBluetooth(String iface) {
         synchronized (mPublicSync) {
             for (String regex : mTetherableBluetoothRegexs) {
                 if (iface.matches(regex)) return true;
@@ -313,35 +315,33 @@
         }
     }
 
+    private int ifaceNameToType(String iface) {
+        if (isWifi(iface)) {
+            return ConnectivityManager.TETHERING_WIFI;
+        } else if (isUsb(iface)) {
+            return ConnectivityManager.TETHERING_USB;
+        } else if (isBluetooth(iface)) {
+            return ConnectivityManager.TETHERING_BLUETOOTH;
+        }
+        return ConnectivityManager.TETHERING_INVALID;
+    }
+
     @Override
     public void interfaceAdded(String iface) {
         if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
-        boolean found = false;
-        boolean usb = false;
         synchronized (mPublicSync) {
-            if (isWifi(iface)) {
-                found = true;
-            }
-            if (isUsb(iface)) {
-                found = true;
-                usb = true;
-            }
-            if (isBluetooth(iface)) {
-                found = true;
-            }
-            if (found == false) {
+            int interfaceType = ifaceNameToType(iface);
+            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
                 if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
                 return;
             }
 
-            TetherInterfaceSM sm = mIfaces.get(iface);
-            if (sm != null) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
+                trackNewTetherableInterface(iface, interfaceType);
+            } else {
                 if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
-                return;
             }
-            sm = new TetherInterfaceSM(iface, mLooper, usb);
-            mIfaces.put(iface, sm);
-            sm.start();
         }
     }
 
@@ -349,15 +349,15 @@
     public void interfaceRemoved(String iface) {
         if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
         synchronized (mPublicSync) {
-            TetherInterfaceSM sm = mIfaces.get(iface);
-            if (sm == null) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
                 if (VDBG) {
                     Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
                 }
                 return;
             }
-            sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
-            mIfaces.remove(iface);
+            tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+            mTetherStates.remove(iface);
         }
     }
 
@@ -413,24 +413,19 @@
      * for the specified interface.
      */
     private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) {
-        boolean isProvisioningRequired = isTetherProvisioningRequired();
+        boolean isProvisioningRequired = enable && isTetherProvisioningRequired();
+        int result;
         switch (type) {
             case ConnectivityManager.TETHERING_WIFI:
-                final WifiManager wifiManager =
-                        (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-                if (wifiManager.setWifiApEnabled(null, enable)) {
-                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                    if (enable && isProvisioningRequired) {
-                        scheduleProvisioningRechecks(type);
-                    }
-                } else{
-                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+                result = setWifiTethering(enable);
+                if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    scheduleProvisioningRechecks(type);
                 }
+                sendTetherResult(receiver, result);
                 break;
             case ConnectivityManager.TETHERING_USB:
-                int result = setUsbTethering(enable);
-                if (enable && isProvisioningRequired &&
-                        result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                result = setUsbTethering(enable);
+                if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                     scheduleProvisioningRechecks(type);
                 }
                 sendTetherResult(receiver, result);
@@ -450,6 +445,20 @@
         }
     }
 
+    private int setWifiTethering(final boolean enable) {
+        synchronized (mPublicSync) {
+            // Note that we're maintaining a predicate that mWifiTetherRequested always matches
+            // our last request to WifiManager re: its AP enabled status.
+            mWifiTetherRequested = enable;
+            final WifiManager wifiManager =
+                    (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+            if (wifiManager.setWifiApEnabled(null /* use existing wifi config */, enable)) {
+                return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+            }
+            return ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+        }
+    }
+
     private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter == null || !adapter.isEnabled()) {
@@ -576,62 +585,62 @@
 
     public int tether(String iface) {
         if (DBG) Log.d(TAG, "Tethering " + iface);
-        TetherInterfaceSM sm = null;
         synchronized (mPublicSync) {
-            sm = mIfaces.get(iface);
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
+                Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+            }
+            // Ignore the error status of the interface.  If the interface is available,
+            // the errors are referring to past tethering attempts anyway.
+            if (tetherState.mLastState != IControlsTethering.STATE_AVAILABLE) {
+                Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+            }
+            tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+            return ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
-        if (sm == null) {
-            Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
-        }
-        if (!sm.isAvailable() && !sm.isErrored()) {
-            Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
-        }
-        sm.sendMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED);
-        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
     public int untether(String iface) {
         if (DBG) Log.d(TAG, "Untethering " + iface);
-        TetherInterfaceSM sm = null;
         synchronized (mPublicSync) {
-            sm = mIfaces.get(iface);
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
+                Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+            }
+            if (tetherState.mLastState != IControlsTethering.STATE_TETHERED) {
+                Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+            }
+            tetherState.mStateMachine.sendMessage(
+                    TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+            return ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
-        if (sm == null) {
-            Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
-        }
-        if (sm.isErrored()) {
-            Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
-        }
-        sm.sendMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED);
-        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
     public void untetherAll() {
-        if (DBG) Log.d(TAG, "Untethering " + mIfaces);
-        for (String iface : mIfaces.keySet()) {
-            untether(iface);
+        synchronized (mPublicSync) {
+            if (DBG) Log.d(TAG, "Untethering " + mTetherStates.keySet());
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                untether(mTetherStates.keyAt(i));
+            }
         }
     }
 
     public int getLastTetherError(String iface) {
-        TetherInterfaceSM sm = null;
         synchronized (mPublicSync) {
-            sm = mIfaces.get(iface);
-            if (sm == null) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
                 Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface +
                         ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
             }
-            return sm.getLastError();
+            return tetherState.mLastError;
         }
     }
 
-    // TODO - move all private methods used only by the state machine into the state machine
-    // to clarify what needs synchronized protection.
     private void sendTetherStateChangedBroadcast() {
         if (!getConnectivityManager().isTetheringSupported()) return;
 
@@ -644,24 +653,22 @@
         boolean bluetoothTethered = false;
 
         synchronized (mPublicSync) {
-            Set ifaces = mIfaces.keySet();
-            for (Object iface : ifaces) {
-                TetherInterfaceSM sm = mIfaces.get(iface);
-                if (sm != null) {
-                    if (sm.isErrored()) {
-                        erroredList.add((String)iface);
-                    } else if (sm.isAvailable()) {
-                        availableList.add((String)iface);
-                    } else if (sm.isTethered()) {
-                        if (isUsb((String)iface)) {
-                            usbTethered = true;
-                        } else if (isWifi((String)iface)) {
-                            wifiTethered = true;
-                      } else if (isBluetooth((String)iface)) {
-                            bluetoothTethered = true;
-                        }
-                        activeList.add((String)iface);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                String iface = mTetherStates.keyAt(i);
+                if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    erroredList.add(iface);
+                } else if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+                    availableList.add(iface);
+                } else if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
+                    if (isUsb(iface)) {
+                        usbTethered = true;
+                    } else if (isWifi(iface)) {
+                        wifiTethered = true;
+                    } else if (isBluetooth(iface)) {
+                        bluetoothTethered = true;
                     }
+                    activeList.add(iface);
                 }
             }
         }
@@ -770,7 +777,7 @@
                     mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
                     // start tethering if we have a request pending
                     if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
-                        tetherUsb(true);
+                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                     }
                     mUsbTetherRequested = false;
                 }
@@ -782,69 +789,72 @@
                     if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
                     mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
                 }
+            } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
+                synchronized (Tethering.this.mPublicSync) {
+                    if (!mWifiTetherRequested) {
+                        // We only care when we're trying to tether via our WiFi interface.
+                        return;
+                    }
+                    int curState =  intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
+                            WifiManager.WIFI_AP_STATE_DISABLED);
+                    switch (curState) {
+                        case WifiManager.WIFI_AP_STATE_ENABLING:
+                            // We can see this state on the way to both enabled and failure states.
+                            break;
+                        case WifiManager.WIFI_AP_STATE_ENABLED:
+                            // Tell an appropriate interface state machine that it should tether.
+                            tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
+                            break;
+                        case WifiManager.WIFI_AP_STATE_DISABLED:
+                        case WifiManager.WIFI_AP_STATE_DISABLING:
+                        case WifiManager.WIFI_AP_STATE_FAILED:
+                        default:
+                            if (DBG) {
+                                Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
+                                    curState);
+                            }
+                            // Tell an appropriate interface state machine that
+                            // it needs to tear itself down.
+                            tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_WIFI);
+                            setWifiTethering(false);
+                            break;
+                    }
+                }
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 updateConfiguration();
             }
         }
     }
 
-    private void tetherUsb(boolean enable) {
-        if (VDBG) Log.d(TAG, "tetherUsb " + enable);
+    private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
+        if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")");
 
-        String[] ifaces = new String[0];
+        String[] ifaces = null;
         try {
             ifaces = mNMService.listInterfaces();
         } catch (Exception e) {
             Log.e(TAG, "Error listing Interfaces", e);
             return;
         }
-        for (String iface : ifaces) {
-            if (isUsb(iface)) {
-                int result = (enable ? tether(iface) : untether(iface));
-                if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                    return;
+        String chosenIface = null;
+        if (ifaces != null) {
+            for (String iface : ifaces) {
+                if (ifaceNameToType(iface) == interfaceType) {
+                    chosenIface = iface;
+                    break;
                 }
             }
         }
-        Log.e(TAG, "unable start or stop USB tethering");
-    }
-
-    // configured when we start tethering and unconfig'd on error or conclusion
-    private boolean configureUsbIface(boolean enabled) {
-        if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
-
-        // toggle the USB interfaces
-        String[] ifaces = new String[0];
-        try {
-            ifaces = mNMService.listInterfaces();
-        } catch (Exception e) {
-            Log.e(TAG, "Error listing Interfaces", e);
-            return false;
+        if (chosenIface == null) {
+            Log.e(TAG, "could not find iface of type " + interfaceType);
+            return;
         }
-        for (String iface : ifaces) {
-            if (isUsb(iface)) {
-                InterfaceConfiguration ifcg = null;
-                try {
-                    ifcg = mNMService.getInterfaceConfig(iface);
-                    if (ifcg != null) {
-                        InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
-                        ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
-                        if (enabled) {
-                            ifcg.setInterfaceUp();
-                        } else {
-                            ifcg.setInterfaceDown();
-                        }
-                        ifcg.clearFlag("running");
-                        mNMService.setInterfaceConfig(iface, ifcg);
-                    }
-                } catch (Exception e) {
-                    Log.e(TAG, "Error configuring interface " + iface, e);
-                    return false;
-                }
-            }
-         }
 
-        return true;
+        int result = (enable ? tether(chosenIface) : untether(chosenIface));
+        if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+            Log.e(TAG, "unable start or stop tethering on iface " + chosenIface);
+            return;
+        }
     }
 
     // TODO - return copies so people can't tamper
@@ -869,7 +879,7 @@
                 if (mRndisEnabled) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        tetherUsb(true);
+                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
@@ -880,7 +890,7 @@
             } else {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    tetherUsb(false);
+                    tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -952,37 +962,27 @@
     public String[] getTetheredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
-            Set keys = mIfaces.keySet();
-            for (Object key : keys) {
-                TetherInterfaceSM sm = mIfaces.get(key);
-                if (sm.isTethered()) {
-                    list.add((String)key);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
+                    list.add(mTetherStates.keyAt(i));
                 }
             }
         }
-        String[] retVal = new String[list.size()];
-        for (int i=0; i < list.size(); i++) {
-            retVal[i] = list.get(i);
-        }
-        return retVal;
+        return list.toArray(new String[list.size()]);
     }
 
     public String[] getTetherableIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
-            Set keys = mIfaces.keySet();
-            for (Object key : keys) {
-                TetherInterfaceSM sm = mIfaces.get(key);
-                if (sm.isAvailable()) {
-                    list.add((String)key);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+                    list.add(mTetherStates.keyAt(i));
                 }
             }
         }
-        String[] retVal = new String[list.size()];
-        for (int i=0; i < list.size(); i++) {
-            retVal[i] = list.get(i);
-        }
-        return retVal;
+        return list.toArray(new String[list.size()]);
     }
 
     public String[] getTetheredDhcpRanges() {
@@ -992,19 +992,14 @@
     public String[] getErroredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
-            Set keys = mIfaces.keySet();
-            for (Object key : keys) {
-                TetherInterfaceSM sm = mIfaces.get(key);
-                if (sm.isErrored()) {
-                    list.add((String)key);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    list.add(mTetherStates.keyAt(i));
                 }
             }
         }
-        String[] retVal = new String[list.size()];
-        for (int i= 0; i< list.size(); i++) {
-            retVal[i] = list.get(i);
-        }
-        return retVal;
+        return list.toArray(new String[list.size()]);
     }
 
     private void maybeLogMessage(State state, int what) {
@@ -1014,401 +1009,6 @@
         }
     }
 
-    class TetherInterfaceSM extends StateMachine {
-        private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
-        // notification from the master SM that it's not in tether mode
-        static final int CMD_TETHER_MODE_DEAD            = BASE_IFACE + 1;
-        // request from the user that it wants to tether
-        static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
-        // request from the user that it wants to untether
-        static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
-        // notification that this interface is down
-        static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
-        // notification that this interface is up
-        static final int CMD_INTERFACE_UP                = BASE_IFACE + 5;
-        // notification from the master SM that it had an error turning on cellular dun
-        static final int CMD_CELL_DUN_ERROR              = BASE_IFACE + 6;
-        // notification from the master SM that it had trouble enabling IP Forwarding
-        static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
-        // notification from the master SM that it had trouble disabling IP Forwarding
-        static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
-        // notification from the master SM that it had trouble starting tethering
-        static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
-        // notification from the master SM that it had trouble stopping tethering
-        static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
-        // notification from the master SM that it had trouble setting the DNS forwarders
-        static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
-        // the upstream connection has changed
-        static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
-
-        private State mDefaultState;
-
-        private State mInitialState;
-        private State mStartingState;
-        private State mTetheredState;
-
-        private State mUnavailableState;
-
-        private boolean mAvailable;
-        private boolean mTethered;
-        int mLastError;
-
-        String mIfaceName;
-        String mMyUpstreamIfaceName;  // may change over time
-
-        boolean mUsb;
-
-        TetherInterfaceSM(String name, Looper looper, boolean usb) {
-            super(name, looper);
-            mIfaceName = name;
-            mUsb = usb;
-            setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-
-            mInitialState = new InitialState();
-            addState(mInitialState);
-            mStartingState = new StartingState();
-            addState(mStartingState);
-            mTetheredState = new TetheredState();
-            addState(mTetheredState);
-            mUnavailableState = new UnavailableState();
-            addState(mUnavailableState);
-
-            setInitialState(mInitialState);
-        }
-
-        public String toString() {
-            String res = new String();
-            res += mIfaceName + " - ";
-            IState current = getCurrentState();
-            if (current == mInitialState) res += "InitialState";
-            if (current == mStartingState) res += "StartingState";
-            if (current == mTetheredState) res += "TetheredState";
-            if (current == mUnavailableState) res += "UnavailableState";
-            if (mAvailable) res += " - Available";
-            if (mTethered) res += " - Tethered";
-            res += " - lastError =" + mLastError;
-            return res;
-        }
-
-        public int getLastError() {
-            synchronized (Tethering.this.mPublicSync) {
-                return mLastError;
-            }
-        }
-
-        private void setLastError(int error) {
-            synchronized (Tethering.this.mPublicSync) {
-                mLastError = error;
-
-                if (isErrored()) {
-                    if (mUsb) {
-                        // note everything's been unwound by this point so nothing to do on
-                        // further error..
-                        Tethering.this.configureUsbIface(false);
-                    }
-                }
-            }
-        }
-
-        public boolean isAvailable() {
-            synchronized (Tethering.this.mPublicSync) {
-                return mAvailable;
-            }
-        }
-
-        private void setAvailable(boolean available) {
-            synchronized (Tethering.this.mPublicSync) {
-                mAvailable = available;
-            }
-        }
-
-        public boolean isTethered() {
-            synchronized (Tethering.this.mPublicSync) {
-                return mTethered;
-            }
-        }
-
-        private void setTethered(boolean tethered) {
-            synchronized (Tethering.this.mPublicSync) {
-                mTethered = tethered;
-            }
-        }
-
-        public boolean isErrored() {
-            synchronized (Tethering.this.mPublicSync) {
-                return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
-            }
-        }
-
-        class InitialState extends State {
-            @Override
-            public void enter() {
-                setAvailable(true);
-                setTethered(false);
-                sendTetherStateChangedBroadcast();
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                switch (message.what) {
-                    case CMD_TETHER_REQUESTED:
-                        setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED,
-                                TetherInterfaceSM.this);
-                        transitionTo(mStartingState);
-                        break;
-                    case CMD_INTERFACE_DOWN:
-                        transitionTo(mUnavailableState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        class StartingState extends State {
-            @Override
-            public void enter() {
-                setAvailable(false);
-                if (mUsb) {
-                    if (!Tethering.this.configureUsbIface(true)) {
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-
-                        transitionTo(mInitialState);
-                        return;
-                    }
-                }
-                sendTetherStateChangedBroadcast();
-
-                // Skipping StartingState
-                transitionTo(mTetheredState);
-            }
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                switch (message.what) {
-                    // maybe a parent class?
-                    case CMD_TETHER_UNREQUESTED:
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        if (mUsb) {
-                            if (!Tethering.this.configureUsbIface(false)) {
-                                setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                                break;
-                            }
-                        }
-                        transitionTo(mInitialState);
-                        break;
-                    case CMD_CELL_DUN_ERROR:
-                    case CMD_IP_FORWARDING_ENABLE_ERROR:
-                    case CMD_IP_FORWARDING_DISABLE_ERROR:
-                    case CMD_START_TETHERING_ERROR:
-                    case CMD_STOP_TETHERING_ERROR:
-                    case CMD_SET_DNS_FORWARDERS_ERROR:
-                        setLastErrorAndTransitionToInitialState(
-                                ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
-                        break;
-                    case CMD_INTERFACE_DOWN:
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        transitionTo(mUnavailableState);
-                        break;
-                    default:
-                        retValue = false;
-                }
-                return retValue;
-            }
-        }
-
-        class TetheredState extends State {
-            @Override
-            public void enter() {
-                try {
-                    mNMService.tetherInterface(mIfaceName);
-                } catch (Exception e) {
-                    Log.e(TAG, "Error Tethering: " + e.toString());
-                    setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
-
-                    try {
-                        mNMService.untetherInterface(mIfaceName);
-                    } catch (Exception ee) {
-                        Log.e(TAG, "Error untethering after failure!" + ee.toString());
-                    }
-                    transitionTo(mInitialState);
-                    return;
-                }
-                if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
-                setAvailable(false);
-                setTethered(true);
-                sendTetherStateChangedBroadcast();
-            }
-
-            private void cleanupUpstream() {
-                if (mMyUpstreamIfaceName != null) {
-                    // note that we don't care about errors here.
-                    // sometimes interfaces are gone before we get
-                    // to remove their rules, which generates errors.
-                    // just do the best we can.
-                    try {
-                        // about to tear down NAT; gather remaining statistics
-                        mStatsService.forceUpdate();
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
-                    }
-                    try {
-                        mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(
-                                TAG, "Exception in removeInterfaceForward: " + e.toString());
-                    }
-                    try {
-                        mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
-                    }
-                    mMyUpstreamIfaceName = null;
-                }
-                return;
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                boolean error = false;
-                switch (message.what) {
-                    case CMD_TETHER_UNREQUESTED:
-                    case CMD_INTERFACE_DOWN:
-                        cleanupUpstream();
-                        try {
-                            mNMService.untetherInterface(mIfaceName);
-                        } catch (Exception e) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
-                            break;
-                        }
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        if (message.what == CMD_TETHER_UNREQUESTED) {
-                            if (mUsb) {
-                                if (!Tethering.this.configureUsbIface(false)) {
-                                    setLastError(
-                                            ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                                }
-                            }
-                            transitionTo(mInitialState);
-                        } else if (message.what == CMD_INTERFACE_DOWN) {
-                            transitionTo(mUnavailableState);
-                        }
-                        if (DBG) Log.d(TAG, "Untethered " + mIfaceName);
-                        break;
-                    case CMD_TETHER_CONNECTION_CHANGED:
-                        String newUpstreamIfaceName = (String)(message.obj);
-                        if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
-                                (mMyUpstreamIfaceName != null &&
-                                mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
-                            if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
-                            break;
-                        }
-                        cleanupUpstream();
-                        if (newUpstreamIfaceName != null) {
-                            try {
-                                mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
-                                mNMService.startInterfaceForwarding(mIfaceName,
-                                        newUpstreamIfaceName);
-                            } catch (Exception e) {
-                                Log.e(TAG, "Exception enabling Nat: " + e.toString());
-                                try {
-                                    mNMService.disableNat(mIfaceName, newUpstreamIfaceName);
-                                } catch (Exception ee) {}
-                                try {
-                                    mNMService.untetherInterface(mIfaceName);
-                                } catch (Exception ee) {}
-
-                                setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
-                                transitionTo(mInitialState);
-                                return true;
-                            }
-                        }
-                        mMyUpstreamIfaceName = newUpstreamIfaceName;
-                        break;
-                    case CMD_CELL_DUN_ERROR:
-                    case CMD_IP_FORWARDING_ENABLE_ERROR:
-                    case CMD_IP_FORWARDING_DISABLE_ERROR:
-                    case CMD_START_TETHERING_ERROR:
-                    case CMD_STOP_TETHERING_ERROR:
-                    case CMD_SET_DNS_FORWARDERS_ERROR:
-                        error = true;
-                        // fall through
-                    case CMD_TETHER_MODE_DEAD:
-                        cleanupUpstream();
-                        try {
-                            mNMService.untetherInterface(mIfaceName);
-                        } catch (Exception e) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
-                            break;
-                        }
-                        if (error) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
-                            break;
-                        }
-                        if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
-                        sendTetherStateChangedBroadcast();
-                        if (mUsb) {
-                            if (!Tethering.this.configureUsbIface(false)) {
-                                setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                            }
-                        }
-                        transitionTo(mInitialState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        class UnavailableState extends State {
-            @Override
-            public void enter() {
-                setAvailable(false);
-                setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                setTethered(false);
-                sendTetherStateChangedBroadcast();
-            }
-            @Override
-            public boolean processMessage(Message message) {
-                boolean retValue = true;
-                switch (message.what) {
-                    case CMD_INTERFACE_UP:
-                        transitionTo(mInitialState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        void setLastErrorAndTransitionToInitialState(int error) {
-            setLastError(error);
-            transitionTo(mInitialState);
-        }
-
-    }
-
     /**
      * A NetworkCallback class that relays information of interest to the
      * tethering master state machine thread for subsequent processing.
@@ -1442,7 +1042,7 @@
      * could/should be moved here.
      */
     class UpstreamNetworkMonitor {
-        final HashMap<Network, NetworkState> mNetworkMap = new HashMap();
+        final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
         NetworkCallback mDefaultNetworkCallback;
         NetworkCallback mDunTetheringCallback;
 
@@ -1520,12 +1120,6 @@
         static final int EVENT_UPSTREAM_LINKPROPERTIES_CHANGED  = BASE_MASTER + 5;
         static final int EVENT_UPSTREAM_LOST                    = BASE_MASTER + 6;
 
-        // This indicates what a timeout event relates to.  A state that
-        // sends itself a delayed timeout event and handles incoming timeout events
-        // should inc this when it is entered and whenever it sends a new timeout event.
-        // We do not flush the old ones.
-        private int mSequenceNumber;
-
         private State mInitialState;
         private State mTetherModeAliveState;
 
@@ -1535,7 +1129,19 @@
         private State mStopTetheringErrorState;
         private State mSetDnsForwardersErrorState;
 
-        private ArrayList<TetherInterfaceSM> mNotifyList;
+        // This list is a little subtle.  It contains all the interfaces that currently are
+        // requesting tethering, regardless of whether these interfaces are still members of
+        // mTetherStates.  This allows us to maintain the following predicates:
+        //
+        // 1) mTetherStates contains the set of all currently existing, tetherable, link state up
+        //    interfaces.
+        // 2) mNotifyList contains all state machines that may have outstanding tethering state
+        //    that needs to be torn down.
+        //
+        // Because we excise interfaces immediately from mTetherStates, we must maintain mNotifyList
+        // so that the garbage collector does not clean up the state machine before it has a chance
+        // to tear itself down.
+        private ArrayList<TetherInterfaceStateMachine> mNotifyList;
 
         private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
         private NetworkCallback mMobileUpstreamCallback;
@@ -1562,7 +1168,7 @@
             mSetDnsForwardersErrorState = new SetDnsForwardersErrorState();
             addState(mSetDnsForwardersErrorState);
 
-            mNotifyList = new ArrayList<TetherInterfaceSM>();
+            mNotifyList = new ArrayList<>();
             setInitialState(mInitialState);
         }
 
@@ -1777,8 +1383,8 @@
             protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
                 if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName);
                 mCurrentUpstreamIface = ifaceName;
-                for (TetherInterfaceSM sm : mNotifyList) {
-                    sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+                for (TetherInterfaceStateMachine sm : mNotifyList) {
+                    sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
                             ifaceName);
                 }
             }
@@ -1845,20 +1451,16 @@
                                 config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
                             ArrayList<Integer> tethered = new ArrayList<Integer>();
                             synchronized (mPublicSync) {
-                                Set ifaces = mIfaces.keySet();
-                                for (Object iface : ifaces) {
-                                    TetherInterfaceSM sm = mIfaces.get(iface);
-                                    if (sm != null && sm.isTethered()) {
-                                        if (isUsb((String)iface)) {
-                                            tethered.add(new Integer(
-                                                    ConnectivityManager.TETHERING_USB));
-                                        } else if (isWifi((String)iface)) {
-                                            tethered.add(new Integer(
-                                                    ConnectivityManager.TETHERING_WIFI));
-                                        } else if (isBluetooth((String)iface)) {
-                                            tethered.add(new Integer(
-                                                    ConnectivityManager.TETHERING_BLUETOOTH));
-                                        }
+                                for (int i = 0; i < mTetherStates.size(); i++) {
+                                    TetherState tetherState = mTetherStates.valueAt(i);
+                                    if (tetherState.mLastState !=
+                                            IControlsTethering.STATE_TETHERED) {
+                                        continue;  // Skip interfaces that aren't tethered.
+                                    }
+                                    String iface = mTetherStates.keyAt(i);
+                                    int interfaceType = ifaceNameToType(iface);
+                                    if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
+                                        tethered.add(new Integer(interfaceType));
                                     }
                                 }
                             }
@@ -1885,26 +1487,22 @@
 
         class InitialState extends TetherMasterUtilState {
             @Override
-            public void enter() {
-            }
-            @Override
             public boolean processMessage(Message message) {
                 maybeLogMessage(this, message.what);
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
-                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
-                        mNotifyList.add(who);
+                        if (mNotifyList.indexOf(who) < 0) {
+                            mNotifyList.add(who);
+                        }
                         transitionTo(mTetherModeAliveState);
                         break;
                     case CMD_TETHER_MODE_UNREQUESTED:
-                        who = (TetherInterfaceSM)message.obj;
+                        who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
-                        int index = mNotifyList.indexOf(who);
-                        if (index != -1) {
-                            mNotifyList.remove(who);
-                        }
+                        mNotifyList.remove(who);
                         break;
                     default:
                         retValue = false;
@@ -1941,26 +1539,28 @@
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
-                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
-                        mNotifyList.add(who);
-                        who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+                        if (mNotifyList.indexOf(who) < 0) {
+                            mNotifyList.add(who);
+                        }
+                        who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
                                 mCurrentUpstreamIface);
                         break;
                     case CMD_TETHER_MODE_UNREQUESTED:
-                        who = (TetherInterfaceSM)message.obj;
+                        who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
-                        int index = mNotifyList.indexOf(who);
-                        if (index != -1) {
+                        if (mNotifyList.remove(who)) {
                             if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who);
-                            mNotifyList.remove(index);
                             if (mNotifyList.isEmpty()) {
                                 turnOffMasterTetherSettings(); // transitions appropriately
                             } else {
                                 if (DBG) {
                                     Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
                                             " live requests:");
-                                    for (Object o : mNotifyList) Log.d(TAG, "  " + o);
+                                    for (TetherInterfaceStateMachine o : mNotifyList) {
+                                        Log.d(TAG, "  " + o);
+                                    }
                                 }
                             }
                         } else {
@@ -2010,7 +1610,7 @@
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
-                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         who.sendMessage(mErrorNotification);
                         break;
                     default:
@@ -2020,8 +1620,7 @@
             }
             void notify(int msgType) {
                 mErrorNotification = msgType;
-                for (Object o : mNotifyList) {
-                    TetherInterfaceSM sm = (TetherInterfaceSM)o;
+                for (TetherInterfaceStateMachine sm : mNotifyList) {
                     sm.sendMessage(msgType);
                 }
             }
@@ -2031,7 +1630,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in setIpForwardingEnabled");
-                notify(TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR);
             }
         }
 
@@ -2039,7 +1638,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in setIpForwardingDisabled");
-                notify(TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR);
             }
         }
 
@@ -2047,7 +1646,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in startTethering");
-                notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR);
                 try {
                     mNMService.setIpForwardingEnabled(false);
                 } catch (Exception e) {}
@@ -2058,7 +1657,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in stopTethering");
-                notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR);
                 try {
                     mNMService.setIpForwardingEnabled(false);
                 } catch (Exception e) {}
@@ -2069,7 +1668,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in setDnsForwarders");
-                notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR);
                 try {
                     mNMService.stopTethering();
                 } catch (Exception e) {}
@@ -2080,9 +1679,11 @@
         }
     }
 
+    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        // Binder.java closes the resource for us.
+        @SuppressWarnings("resource")
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-
         if (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
             pw.println("Permission Denial: can't dump ConnectivityService.Tether " +
@@ -2102,12 +1703,67 @@
 
             pw.println("Tether state:");
             pw.increaseIndent();
-            for (Object o : mIfaces.values()) {
-                pw.println(o);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                final String iface = mTetherStates.keyAt(i);
+                final TetherState tetherState = mTetherStates.valueAt(i);
+                pw.print(iface + " - ");
+
+                switch (tetherState.mLastState) {
+                    case IControlsTethering.STATE_UNAVAILABLE:
+                        pw.print("UnavailableState");
+                        break;
+                    case IControlsTethering.STATE_AVAILABLE:
+                        pw.print("AvailableState");
+                        break;
+                    case IControlsTethering.STATE_TETHERED:
+                        pw.print("TetheredState");
+                        break;
+                    default:
+                        pw.print("UnknownState");
+                        break;
+                }
+                pw.println(" - lastError = " + tetherState.mLastError);
             }
             pw.decreaseIndent();
         }
         pw.decreaseIndent();
-        return;
+    }
+
+    @Override
+    public void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
+                                           int state, int error) {
+        synchronized (mPublicSync) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState != null && tetherState.mStateMachine.equals(who)) {
+                tetherState.mLastState = state;
+                tetherState.mLastError = error;
+            } else {
+                if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
+            }
+        }
+
+        if (DBG) {
+            Log.d(TAG, "iface " + iface + " notified that it was in state " + state +
+                    " with error " + error);
+        }
+
+        switch (state) {
+            case IControlsTethering.STATE_UNAVAILABLE:
+            case IControlsTethering.STATE_AVAILABLE:
+                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who);
+                break;
+            case IControlsTethering.STATE_TETHERED:
+                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, who);
+                break;
+        }
+        sendTetherStateChangedBroadcast();
+    }
+
+    private void trackNewTetherableInterface(String iface, int interfaceType) {
+        TetherState tetherState;
+        tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
+                interfaceType, mNMService, mStatsService, this));
+        mTetherStates.put(iface, tetherState);
+        tetherState.mStateMachine.start();
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
new file mode 100644
index 0000000..449b8a8
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
@@ -0,0 +1,39 @@
+/*
+ * 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.connectivity.tethering;
+
+/**
+ * @hide
+ *
+ * Interface with methods necessary to notify that a given interface is ready for tethering.
+ */
+public interface IControlsTethering {
+    public final int STATE_UNAVAILABLE = 0;
+    public final int STATE_AVAILABLE = 1;
+    public final int STATE_TETHERED = 2;
+
+    /**
+     * Notify that |who| has changed its tethering state.  This may be called from any thread.
+     *
+     * @param iface a network interface (e.g. "wlan0")
+     * @param who corresponding instance of a TetherInterfaceStateMachine
+     * @param state one of IControlsTethering.STATE_*
+     * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+     */
+    void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
+                                    int state, int lastError);
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
new file mode 100644
index 0000000..aebeb69
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -0,0 +1,322 @@
+/*
+ * 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.connectivity.tethering;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.net.InetAddress;
+
+/**
+ * @hide
+ *
+ * Tracks the eligibility of a given network interface for tethering.
+ */
+public class TetherInterfaceStateMachine extends StateMachine {
+    private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
+    private static final int USB_PREFIX_LENGTH = 24;
+    private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
+    private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
+
+    private final static String TAG = "TetherInterfaceSM";
+    private final static boolean DBG = false;
+    private final static boolean VDBG = false;
+    private static final Class[] messageClasses = {
+            TetherInterfaceStateMachine.class
+    };
+    private static final SparseArray<String> sMagicDecoderRing =
+            MessageUtils.findMessageNames(messageClasses);
+
+    private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
+    // request from the user that it wants to tether
+    public static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
+    // request from the user that it wants to untether
+    public static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
+    // notification that this interface is down
+    public static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
+    // notification from the master SM that it had trouble enabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
+    // notification from the master SM that it had trouble disabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+    // notification from the master SM that it had trouble starting tethering
+    public static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
+    // notification from the master SM that it had trouble stopping tethering
+    public static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
+    // notification from the master SM that it had trouble setting the DNS forwarders
+    public static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
+    // the upstream connection has changed
+    public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
+
+    private final State mInitialState;
+    private final State mTetheredState;
+    private final State mUnavailableState;
+
+    private final INetworkManagementService mNMService;
+    private final INetworkStatsService mStatsService;
+    private final IControlsTethering mTetherController;
+
+    private final String mIfaceName;
+    private final int mInterfaceType;
+
+    private int mLastError;
+    private String mMyUpstreamIfaceName;  // may change over time
+
+    public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
+                    INetworkManagementService nMService, INetworkStatsService statsService,
+                    IControlsTethering tetherController) {
+        super(ifaceName, looper);
+        mNMService = nMService;
+        mStatsService = statsService;
+        mTetherController = tetherController;
+        mIfaceName = ifaceName;
+        mInterfaceType = interfaceType;
+        mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+
+        mInitialState = new InitialState();
+        addState(mInitialState);
+        mTetheredState = new TetheredState();
+        addState(mTetheredState);
+        mUnavailableState = new UnavailableState();
+        addState(mUnavailableState);
+
+        setInitialState(mInitialState);
+    }
+
+    // configured when we start tethering and unconfig'd on error or conclusion
+    private boolean configureIfaceIp(boolean enabled) {
+        if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
+
+        String ipAsString = null;
+        int prefixLen = 0;
+        if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
+            ipAsString = USB_NEAR_IFACE_ADDR;
+            prefixLen = USB_PREFIX_LENGTH;
+        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
+            ipAsString = WIFI_HOST_IFACE_ADDR;
+            prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+        } else {
+            // Nothing to do, BT does this elsewhere.
+            return true;
+        }
+
+        InterfaceConfiguration ifcg = null;
+        try {
+            ifcg = mNMService.getInterfaceConfig(mIfaceName);
+            if (ifcg != null) {
+                InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
+                ifcg.setLinkAddress(new LinkAddress(addr, prefixLen));
+                if (enabled) {
+                    ifcg.setInterfaceUp();
+                } else {
+                    ifcg.setInterfaceDown();
+                }
+                ifcg.clearFlag("running");
+                mNMService.setInterfaceConfig(mIfaceName, ifcg);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error configuring interface " + mIfaceName, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void maybeLogMessage(State state, int what) {
+        if (DBG) {
+            Log.d(TAG, state.getName() + " got " +
+                    sMagicDecoderRing.get(what, Integer.toString(what)));
+        }
+    }
+
+    class InitialState extends State {
+        @Override
+        public void enter() {
+            mTetherController.notifyInterfaceStateChange(
+                    mIfaceName, TetherInterfaceStateMachine.this,
+                    IControlsTethering.STATE_AVAILABLE, mLastError);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            boolean retValue = true;
+            switch (message.what) {
+                case CMD_TETHER_REQUESTED:
+                    mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+                    transitionTo(mTetheredState);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    break;
+                default:
+                    retValue = false;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    class TetheredState extends State {
+        @Override
+        public void enter() {
+            if (!configureIfaceIp(true)) {
+                mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+                transitionTo(mInitialState);
+                return;
+            }
+
+            try {
+                mNMService.tetherInterface(mIfaceName);
+            } catch (Exception e) {
+                Log.e(TAG, "Error Tethering: " + e.toString());
+                mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+                transitionTo(mInitialState);
+                return;
+            }
+            if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+            mTetherController.notifyInterfaceStateChange(
+                    mIfaceName, TetherInterfaceStateMachine.this,
+                    IControlsTethering.STATE_TETHERED, mLastError);
+        }
+
+        @Override
+        public void exit() {
+            // Note that at this point, we're leaving the tethered state.  We can fail any
+            // of these operations, but it doesn't really change that we have to try them
+            // all in sequence.
+            cleanupUpstream();
+
+            try {
+                mNMService.untetherInterface(mIfaceName);
+            } catch (Exception ee) {
+                mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+                Log.e(TAG, "Failed to untether interface: " + ee.toString());
+            }
+
+            configureIfaceIp(false);
+        }
+
+        private void cleanupUpstream() {
+            if (mMyUpstreamIfaceName != null) {
+                // note that we don't care about errors here.
+                // sometimes interfaces are gone before we get
+                // to remove their rules, which generates errors.
+                // just do the best we can.
+                try {
+                    // about to tear down NAT; gather remaining statistics
+                    mStatsService.forceUpdate();
+                } catch (Exception e) {
+                    if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+                }
+                try {
+                    mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
+                } catch (Exception e) {
+                    if (VDBG) Log.e(
+                            TAG, "Exception in removeInterfaceForward: " + e.toString());
+                }
+                try {
+                    mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
+                } catch (Exception e) {
+                    if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+                }
+                mMyUpstreamIfaceName = null;
+            }
+            return;
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            boolean retValue = true;
+            switch (message.what) {
+                case CMD_TETHER_UNREQUESTED:
+                    transitionTo(mInitialState);
+                    if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
+                    break;
+                case CMD_TETHER_CONNECTION_CHANGED:
+                    String newUpstreamIfaceName = (String)(message.obj);
+                    if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
+                            (mMyUpstreamIfaceName != null &&
+                            mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
+                        if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
+                        break;
+                    }
+                    cleanupUpstream();
+                    if (newUpstreamIfaceName != null) {
+                        try {
+                            mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
+                            mNMService.startInterfaceForwarding(mIfaceName,
+                                    newUpstreamIfaceName);
+                        } catch (Exception e) {
+                            Log.e(TAG, "Exception enabling Nat: " + e.toString());
+                            mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+                            transitionTo(mInitialState);
+                            return true;
+                        }
+                    }
+                    mMyUpstreamIfaceName = newUpstreamIfaceName;
+                    break;
+                case CMD_IP_FORWARDING_ENABLE_ERROR:
+                case CMD_IP_FORWARDING_DISABLE_ERROR:
+                case CMD_START_TETHERING_ERROR:
+                case CMD_STOP_TETHERING_ERROR:
+                case CMD_SET_DNS_FORWARDERS_ERROR:
+                    mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+                    transitionTo(mInitialState);
+                    break;
+                default:
+                    retValue = false;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    /**
+     * This state is terminal for the per interface state machine.  At this
+     * point, the master state machine should have removed this interface
+     * specific state machine from its list of possible recipients of
+     * tethering requests.  The state machine itself will hang around until
+     * the garbage collector finds it.
+     */
+    class UnavailableState extends State {
+        @Override
+        public void enter() {
+            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+            mTetherController.notifyInterfaceStateChange(
+                    mIfaceName, TetherInterfaceStateMachine.this,
+                    IControlsTethering.STATE_UNAVAILABLE, mLastError);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
index fe6fb1f..b25ef175 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -64,11 +64,12 @@
         mIntent = new Intent().setComponent(componentName);
     }
 
-    public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) {
+    public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(
+            int hashPrefix[], int prefixMask) {
         throwIfCalledOnMainThread();
         try {
             return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
-                    getRemoteInstanceLazy(), hashPrefix);
+                    getRemoteInstanceLazy(), hashPrefix, prefixMask);
         } catch (RemoteException re) {
         } catch (TimeoutException te) {
         } finally {
@@ -177,10 +178,10 @@
         }
 
         public List<EphemeralResolveInfo> getEphemeralResolveInfoList(
-                IEphemeralResolver target, int hashPrefix)
+                IEphemeralResolver target, int hashPrefix[], int prefixMask)
                         throws RemoteException, TimeoutException {
             final int sequence = onBeforeRemoteCall();
-            target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence);
+            target.getEphemeralResolveInfoList(mCallback, hashPrefix, prefixMask, sequence);
             return getResultTimed(sequence);
         }
     }
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationState.java b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
index c6e7911..a4e9d10 100644
--- a/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
+++ b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
@@ -96,7 +96,12 @@
             if (i > 0) {
                 sb.append(" ");
             }
-            sb.append(mHosts.valueAt(i));
+            String host = mHosts.valueAt(i);
+            // "*.example.tld" is validated via https://example.tld
+            if (host.startsWith("*.")) {
+                host = host.substring(2);
+            }
+            sb.append(host);
         }
         return sb.toString();
     }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ffe8f75..43a0b91 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -745,9 +745,6 @@
             @Override
             public void onShortcutChanged(@NonNull String packageName,
                     @UserIdInt int userId) {
-                if (!ShortcutService.FEATURE_ENABLED) {
-                    return;
-                }
                 postToPackageMonitorHandler(() -> onShortcutChangedInner(packageName, userId));
             }
 
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 649a27c..df91f4a 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -54,6 +54,7 @@
 
     // TODO: Evaluate the need for WeakReferences here.
     private List<PackageParser.Package> mDexoptPackages;
+    private int completeSize;
 
     public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
         this.mContext = context;
@@ -91,6 +92,7 @@
             mDexoptPackages = PackageManagerServiceUtils.getPackagesForDexopt(
                     mPackageManagerService.mPackages.values(), mPackageManagerService);
         }
+        completeSize = mDexoptPackages.size();
     }
 
     @Override
@@ -111,6 +113,14 @@
     }
 
     @Override
+    public synchronized float getProgress() throws RemoteException {
+        if (completeSize == 0) {
+            return 1f;
+        }
+        return (completeSize - mDexoptPackages.size()) / ((float)completeSize);
+    }
+
+    @Override
     public synchronized void dexoptNextPackage() throws RemoteException {
         if (mDexoptPackages == null) {
             throw new IllegalStateException("dexoptNextPackage() called before prepare()");
diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
index ea9cf17..e8fdfa5 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
@@ -46,6 +46,8 @@
                     return runOtaDone();
                 case "step":
                     return runOtaStep();
+                case "progress":
+                    return runOtaProgress();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -81,6 +83,13 @@
         return 0;
     }
 
+    private int runOtaProgress() throws RemoteException {
+        final float progress = mInterface.getProgress();
+        final PrintWriter pw = getOutPrintWriter();
+        pw.format("%.2f", progress);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2986a12..2c4d732 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -125,6 +125,7 @@
 import android.content.pm.ComponentInfo;
 import android.content.pm.EphemeralApplicationInfo;
 import android.content.pm.EphemeralResolveInfo;
+import android.content.pm.EphemeralResolveInfo.EphemeralDigest;
 import android.content.pm.EphemeralResolveInfo.EphemeralResolveIntentInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IOnPermissionsChangeListener;
@@ -197,6 +198,7 @@
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
+import android.provider.Settings.Global;
 import android.security.KeyStore;
 import android.security.SystemKeyStore;
 import android.system.ErrnoException;
@@ -362,13 +364,13 @@
     static final boolean DEBUG_DEXOPT = false;
 
     private static final boolean DEBUG_ABI_SELECTION = false;
-    private static final boolean DEBUG_EPHEMERAL = false;
+    private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
     private static final boolean DEBUG_TRIAGED_MISSING = false;
     private static final boolean DEBUG_APP_DATA = false;
 
     static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
 
-    private static final boolean DISABLE_EPHEMERAL_APPS = true;
+    private static final boolean DISABLE_EPHEMERAL_APPS = !Build.IS_DEBUGGABLE;
 
     private static final int RADIO_UID = Process.PHONE_UID;
     private static final int LOG_UID = Process.LOG_UID;
@@ -462,6 +464,9 @@
 
     private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
 
+    private static int DEFAULT_EPHEMERAL_HASH_PREFIX_MASK = 0xFFFFFFFF;
+    private static int DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT = 5;
+
     /** Permission grant: not grant the permission. */
     private static final int GRANT_DENIED = 1;
 
@@ -2944,17 +2949,20 @@
     private @Nullable ComponentName getEphemeralResolverLPr() {
         final String[] packageArray =
                 mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage);
-        if (packageArray.length == 0) {
+        if (packageArray.length == 0 && !Build.IS_DEBUGGABLE) {
             if (DEBUG_EPHEMERAL) {
                 Slog.d(TAG, "Ephemeral resolver NOT found; empty package list");
             }
             return null;
         }
 
+        final int resolveFlags =
+                MATCH_DIRECT_BOOT_AWARE
+                | MATCH_DIRECT_BOOT_UNAWARE
+                | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0);
         final Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE);
         final List<ResolveInfo> resolvers = queryIntentServicesInternal(resolverIntent, null,
-                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM);
+                resolveFlags, UserHandle.USER_SYSTEM);
 
         final int N = resolvers.size();
         if (N == 0) {
@@ -2973,7 +2981,7 @@
             }
 
             final String packageName = info.serviceInfo.packageName;
-            if (!possiblePackages.contains(packageName)) {
+            if (!possiblePackages.contains(packageName) && !Build.IS_DEBUGGABLE) {
                 if (DEBUG_EPHEMERAL) {
                     Slog.d(TAG, "Ephemeral resolver not in allowed package list;"
                             + " pkg: " + packageName + ", info:" + info);
@@ -2998,9 +3006,12 @@
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
 
+        final int resolveFlags =
+                MATCH_DIRECT_BOOT_AWARE
+                | MATCH_DIRECT_BOOT_UNAWARE
+                | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0);
         final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
-                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM);
+                resolveFlags, UserHandle.USER_SYSTEM);
         if (matches.size() == 0) {
             return null;
         } else if (matches.size() == 1) {
@@ -4989,48 +5000,44 @@
 
     private EphemeralResolveInfo getEphemeralResolveInfo(Intent intent, String resolvedType,
             int userId) {
-        MessageDigest digest = null;
-        try {
-            digest = MessageDigest.getInstance(EphemeralResolveInfo.SHA_ALGORITHM);
-        } catch (NoSuchAlgorithmException e) {
-            // If we can't create a digest, ignore ephemeral apps.
-            return null;
-        }
-
-        final byte[] hostBytes = intent.getData().getHost().getBytes();
-        final byte[] digestBytes = digest.digest(hostBytes);
-        int shaPrefix =
-                digestBytes[0] << 24
-                | digestBytes[1] << 16
-                | digestBytes[2] << 8
-                | digestBytes[3] << 0;
+        final int ephemeralPrefixMask = Global.getInt(mContext.getContentResolver(),
+                Global.EPHEMERAL_HASH_PREFIX_MASK, DEFAULT_EPHEMERAL_HASH_PREFIX_MASK);
+        final int ephemeralPrefixCount = Global.getInt(mContext.getContentResolver(),
+                Global.EPHEMERAL_HASH_PREFIX_COUNT, DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT);
+        final EphemeralDigest digest = new EphemeralDigest(intent.getData(), ephemeralPrefixCount);
+        final int[] shaPrefix = digest.getDigestPrefix();
+        final byte[][] digestBytes = digest.getDigestBytes();
         final List<EphemeralResolveInfo> ephemeralResolveInfoList =
-                mEphemeralResolverConnection.getEphemeralResolveInfoList(shaPrefix);
+                mEphemeralResolverConnection.getEphemeralResolveInfoList(
+                        shaPrefix, ephemeralPrefixMask);
         if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) {
             // No hash prefix match; there are no ephemeral apps for this domain.
             return null;
         }
-        for (int i = ephemeralResolveInfoList.size() - 1; i >= 0; --i) {
-            EphemeralResolveInfo ephemeralApplication = ephemeralResolveInfoList.get(i);
-            if (!Arrays.equals(digestBytes, ephemeralApplication.getDigestBytes())) {
-                continue;
-            }
-            final List<IntentFilter> filters = ephemeralApplication.getFilters();
-            // No filters; this should never happen.
-            if (filters.isEmpty()) {
-                continue;
-            }
-            // We have a domain match; resolve the filters to see if anything matches.
-            final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver();
-            for (int j = filters.size() - 1; j >= 0; --j) {
-                final EphemeralResolveIntentInfo intentInfo =
-                        new EphemeralResolveIntentInfo(filters.get(j), ephemeralApplication);
-                ephemeralResolver.addFilter(intentInfo);
-            }
-            List<EphemeralResolveInfo> matchedResolveInfoList = ephemeralResolver.queryIntent(
-                    intent, resolvedType, false /*defaultOnly*/, userId);
-            if (!matchedResolveInfoList.isEmpty()) {
-                return matchedResolveInfoList.get(0);
+
+        // Go in reverse order so we match the narrowest scope first.
+        for (int i = shaPrefix.length; i >= 0 ; --i) {
+            for (EphemeralResolveInfo ephemeralApplication : ephemeralResolveInfoList) {
+                if (!Arrays.equals(digestBytes[i], ephemeralApplication.getDigestBytes())) {
+                    continue;
+                }
+                final List<IntentFilter> filters = ephemeralApplication.getFilters();
+                // No filters; this should never happen.
+                if (filters.isEmpty()) {
+                    continue;
+                }
+                // We have a domain match; resolve the filters to see if anything matches.
+                final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver();
+                for (int j = filters.size() - 1; j >= 0; --j) {
+                    final EphemeralResolveIntentInfo intentInfo =
+                            new EphemeralResolveIntentInfo(filters.get(j), ephemeralApplication);
+                    ephemeralResolver.addFilter(intentInfo);
+                }
+                List<EphemeralResolveInfo> matchedResolveInfoList = ephemeralResolver.queryIntent(
+                        intent, resolvedType, false /*defaultOnly*/, userId);
+                if (!matchedResolveInfoList.isEmpty()) {
+                    return matchedResolveInfoList.get(0);
+                }
             }
         }
         // Hash or filter mis-match; no ephemeral apps for this domain.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index dfd6dfe..5126305 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4011,7 +4011,7 @@
         file.delete();
         removeCrossProfileIntentFiltersLPw(userId);
 
-        mRuntimePermissionsPersistence.onUserRemoved(userId);
+        mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
 
         writePackageListLPr();
     }
@@ -5108,7 +5108,7 @@
             }
         }
 
-        private void onUserRemoved(int userId) {
+        private void onUserRemovedLPw(int userId) {
             // Make sure we do not
             mHandler.removeMessages(userId);
 
@@ -5119,6 +5119,9 @@
             for (SettingBase sb : mSharedUsers.values()) {
                 revokeRuntimePermissionsAndClearFlags(sb, userId);
             }
+
+            mDefaultPermissionsGranted.delete(userId);
+            mFingerprints.remove(userId);
         }
 
         private void revokeRuntimePermissionsAndClearFlags(SettingBase sb, int userId) {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 76d47a8..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;
@@ -80,26 +82,31 @@
      * Called when the new package can't receive the backup, due to signature or version mismatch.
      */
     @Override
-    protected void onRestoreBlocked(ShortcutService s) {
+    protected void onRestoreBlocked() {
         final ArrayList<PackageWithUser> pinnedPackages =
                 new ArrayList<>(mPinnedShortcuts.keySet());
         mPinnedShortcuts.clear();
         for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
             final PackageWithUser pu = pinnedPackages.get(i);
-            s.getPackageShortcutsLocked(pu.packageName, pu.userId)
-                    .refreshPinnedFlags(s);
+            final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName);
+            if (p != null) {
+                p.refreshPinnedFlags();
+            }
         }
     }
 
     @Override
-    protected void onRestored(ShortcutService s) {
+    protected void onRestored() {
         // Nothing to do.
     }
 
-    public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
+    public void pinShortcuts(@UserIdInt int packageUserId,
             @NonNull String packageName, @NonNull List<String> ids) {
         final ShortcutPackage packageShortcuts =
-                s.getPackageShortcutsLocked(packageName, packageUserId);
+                mShortcutUser.getPackageShortcutsIfExists(packageName);
+        if (packageShortcuts == null) {
+            return; // No need to instantiate.
+        }
 
         final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
 
@@ -120,13 +127,14 @@
                 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);
                 }
             }
             mPinnedShortcuts.put(pu, newSet);
         }
-        packageShortcuts.refreshPinnedFlags(s);
+        packageShortcuts.refreshPinnedFlags();
     }
 
     /**
@@ -240,7 +248,7 @@
         return ret;
     }
 
-    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         pw.println();
 
         pw.print(prefix);
@@ -252,7 +260,7 @@
         pw.print(getOwnerUserId());
         pw.println();
 
-        getPackageInfo().dump(s, pw, prefix + "  ");
+        getPackageInfo().dump(pw, prefix + "  ");
         pw.println();
 
         final int size = mPinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 151f61e..ace14ac 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,15 +20,20 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
 import android.os.PersistableBundle;
 import android.text.format.Formatter;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.ShortcutService.ShortcutOperation;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -38,15 +43,23 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
 
 /**
  * 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;
+    private static final String TAG_VERIFY = ShortcutService.TAG + ".verify";
 
     static final String TAG_ROOT = "package";
     private static final String TAG_INTENT_EXTRAS = "intent-extras";
@@ -55,18 +68,25 @@
     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";
     private static final String ATTR_ACTIVITY = "activity";
     private static final String ATTR_TITLE = "title";
+    private static final String ATTR_TITLE_RES_ID = "titleid";
+    private static final String ATTR_TITLE_RES_NAME = "titlename";
     private static final String ATTR_TEXT = "text";
+    private static final String ATTR_TEXT_RES_ID = "textid";
+    private static final String ATTR_TEXT_RES_NAME = "textname";
+    private static final String ATTR_DISABLED_MESSAGE = "dmessage";
+    private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
+    private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
     private static final String ATTR_INTENT = "intent";
-    private static final String ATTR_WEIGHT = "weight";
+    private static final String ATTR_RANK = "rank";
     private static final String ATTR_TIMESTAMP = "timestamp";
     private static final String ATTR_FLAGS = "flags";
-    private static final String ATTR_ICON_RES = "icon-res";
+    private static final String ATTR_ICON_RES_ID = "icon-res";
+    private static final String ATTR_ICON_RES_NAME = "icon-resname";
     private static final String ATTR_BITMAP_PATH = "bitmap-path";
 
     private static final String NAME_CATEGORIES = "categories";
@@ -80,11 +100,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;
@@ -98,17 +113,16 @@
 
     private long mLastKnownForegroundElapsedTime;
 
-    private ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
+    private ShortcutPackage(ShortcutUser shortcutUser,
             int packageUserId, String packageName, ShortcutPackageInfo spi) {
         super(shortcutUser, packageUserId, packageName,
                 spi != null ? spi : ShortcutPackageInfo.newEmpty());
 
-        mPackageUid = s.injectGetPackageUid(packageName, packageUserId);
+        mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId);
     }
 
-    public ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
-            int packageUserId, String packageName) {
-        this(s, shortcutUser, packageUserId, packageName, null);
+    public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) {
+        this(shortcutUser, packageUserId, packageName, null);
     }
 
     @Override
@@ -122,37 +136,46 @@
     }
 
     /**
-     * Called when a shortcut is about to be published.  At this point we know the publisher package
+     * Called when a shortcut is about to be published.  At this point we know the publisher
+     * package
      * 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(ShortcutService s) {
+    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 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 ShortcutService s = mShortcutUser.mService;
+
+            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());
             }
         }
     }
 
+    @Nullable
+    public Resources getPackageResources() {
+        return mShortcutUser.mService.injectGetResourcesForApplicationAsUser(
+                getPackageName(), getPackageUserId());
+    }
+
     @Override
-    protected void onRestoreBlocked(ShortcutService s) {
+    protected void onRestoreBlocked() {
         // Can't restore due to version/signature mismatch.  Remove all shortcuts.
         mShortcuts.clear();
     }
 
     @Override
-    protected void onRestored(ShortcutService s) {
+    protected void onRestored() {
         // Because some launchers may not have been restored (e.g. allowBackup=false),
         // we need to re-calculate the pinned shortcuts.
-        refreshPinnedFlags(s);
+        refreshPinnedFlags();
     }
 
     /**
@@ -163,19 +186,48 @@
         return mShortcuts.get(id);
     }
 
-    private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
-            @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) {
-            s.removeIcon(getPackageUserId(), shortcut);
-            shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
+            mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
+            shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
+                    | ShortcutInfo.FLAG_MANIFEST);
         }
         return shortcut;
     }
 
-    void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
-        deleteShortcut(s, newShortcut.getId());
+    private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
+        final ShortcutService s = mShortcutUser.mService;
+
+        deleteShortcutInner(newShortcut.getId());
+
+        // Extract Icon and update the icon res ID and the bitmap path.
         s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
+        s.fixUpShortcutResourceNamesAndValues(newShortcut);
         mShortcuts.put(newShortcut.getId(), newShortcut);
     }
 
@@ -184,52 +236,53 @@
      *
      * It checks the max number of dynamic shortcuts.
      */
-    public void addDynamicShortcut(@NonNull ShortcutService s,
-            @NonNull ShortcutInfo newShortcut) {
+    public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
 
-        onShortcutPublish(s);
+        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.
-        s.enforceMaxDynamicShortcuts(newDynamicCount);
+        // TODO Check max dynamic count.
+        // mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
 
-        // Okay, make it dynamic and add.
+        // If it was originally pinned, the new one should be pinned too.
         if (wasPinned) {
             newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
         }
 
-        addShortcut(s, newShortcut);
-        mDynamicShortcutCount = newDynamicCount;
+        addShortcutInner(newShortcut);
     }
 
     /**
      * Remove all shortcuts that aren't pinned nor dynamic.
      */
-    private void removeOrphans(@NonNull ShortcutService s) {
+    private void removeOrphans() {
         ArrayList<String> removeList = null; // Lazily initialize.
 
         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<>();
@@ -238,7 +291,7 @@
         }
         if (removeList != null) {
             for (int i = removeList.size() - 1; i >= 0; i--) {
-                deleteShortcut(s, removeList.get(i));
+                deleteShortcutInner(removeList.get(i));
             }
         }
     }
@@ -246,30 +299,85 @@
     /**
      * Remove all dynamic shortcuts.
      */
-    public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
+    public void deleteAllDynamicShortcuts() {
+        final long now = mShortcutUser.mService.injectCurrentTimeMillis();
+
+        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.setTimestamp(now);
+                si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+                si.setRank(0); // It may still be pinned, so clear the rank.
+            }
         }
-        removeOrphans(s);
-        mDynamicShortcutCount = 0;
+        if (changed) {
+            removeOrphans();
+        }
     }
 
     /**
-     * Remove a dynamic shortcut by ID.
+     * Remove a dynamic shortcut by ID.  It'll be removed from the dynamic set, but if the shortcut
+     * is pinned, it'll remain as a pinned shortcut, and is still enabled.
      */
-    public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
+    public void deleteDynamicWithId(@NonNull String shortcutId) {
+        deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+    }
+
+    /**
+     * Disable a dynamic shortcut by ID.  It'll be removed from the dynamic set, but if the shortcut
+     * is pinned, it'll remain as a pinned shortcut but will be disabled.
+     */
+    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);
+
+                mShortcutUser.mService.fixUpShortcutResourceNamesAndValues(disabled);
+            }
+        }
+    }
+
+    @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.setRank(0);
+            oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
+            if (disable) {
+                oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
+            }
+            oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
+
+            return oldShortcut;
         } else {
-            deleteShortcut(s, 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);
         }
     }
 
@@ -279,14 +387,15 @@
      *
      * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
      */
-    public void refreshPinnedFlags(@NonNull ShortcutService s) {
+    public void refreshPinnedFlags() {
         // First, un-pin all shortcuts
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
         }
 
         // Then, for the pinned set for each launcher, set the pin flag one by one.
-        s.getUserShortcutsLocked(getPackageUserId()).forAllLaunchers(launcherShortcuts -> {
+        mShortcutUser.mService.getUserShortcutsLocked(getPackageUserId())
+                .forAllLaunchers(launcherShortcuts -> {
             final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
                     getPackageName(), getPackageUserId());
 
@@ -308,7 +417,7 @@
         });
 
         // Lastly, remove the ones that are no longer pinned nor dynamic.
-        removeOrphans(s);
+        removeOrphans();
     }
 
     /**
@@ -317,8 +426,10 @@
      * <p>This takes care of the resetting the counter for foreground apps as well as after
      * locale changes.
      */
-    public int getApiCallCount(@NonNull ShortcutService s) {
-        mShortcutUser.resetThrottlingIfNeeded(s);
+    public int getApiCallCount() {
+        mShortcutUser.resetThrottlingIfNeeded();
+
+        final ShortcutService s = mShortcutUser.mService;
 
         // Reset the counter if:
         // - the package is in foreground now.
@@ -328,7 +439,7 @@
                 || mLastKnownForegroundElapsedTime
                     < s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) {
             mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
-            resetRateLimiting(s);
+            resetRateLimiting();
         }
 
         // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
@@ -349,8 +460,8 @@
         // If not reset yet, then reset.
         if (mLastResetTime < last) {
             if (ShortcutService.DEBUG) {
-                Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting",
-                        mLastResetTime, now, last));
+                Slog.d(TAG, String.format("%s: last reset=%d, now=%d, last=%d: resetting",
+                        getPackageName(), mLastResetTime, now, last));
             }
             mApiCallCount = 0;
             mLastResetTime = last;
@@ -365,8 +476,10 @@
      * <p>This takes care of the resetting the counter for foreground apps as well as after
      * locale changes, which is done internally by {@link #getApiCallCount}.
      */
-    public boolean tryApiCall(@NonNull ShortcutService s) {
-        if (getApiCallCount(s) >= s.mMaxUpdatesPerInterval) {
+    public boolean tryApiCall() {
+        final ShortcutService s = mShortcutUser.mService;
+
+        if (getApiCallCount() >= s.mMaxUpdatesPerInterval) {
             return false;
         }
         mApiCallCount++;
@@ -374,13 +487,13 @@
         return true;
     }
 
-    public void resetRateLimiting(@NonNull ShortcutService s) {
+    public void resetRateLimiting() {
         if (ShortcutService.DEBUG) {
             Slog.d(TAG, "resetRateLimiting: " + getPackageName());
         }
         if (mApiCallCount > 0) {
             mApiCallCount = 0;
-            s.scheduleSaveUser(getOwnerUserId());
+            mShortcutUser.mService.scheduleSaveUser(getOwnerUserId());
         }
     }
 
@@ -392,9 +505,9 @@
     /**
      * Find all shortcuts that match {@code query}.
      */
-    public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
+    public void findAll(@NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
-        findAll(s, result, query, cloneFlag, null, 0);
+        findAll(result, query, cloneFlag, null, 0);
     }
 
     /**
@@ -404,7 +517,7 @@
      * by the calling launcher will not be included in the result, and also "isPinned" will be
      * adjusted for the caller too.
      */
-    public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
+    public void findAll(@NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
             @Nullable String callingLauncher, int launcherUserId) {
         if (getPackageInfo().isShadow()) {
@@ -412,6 +525,8 @@
             return;
         }
 
+        final ShortcutService s = mShortcutUser.mService;
+
         // Set of pinned shortcuts by the calling launcher.
         final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
                 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
@@ -420,22 +535,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) {
@@ -452,30 +564,124 @@
     }
 
     /**
-     * Called when the package is updated.  If there are shortcuts with resource icons, update
-     * their timestamps.
+     * Return the filenames (excluding path names) of icon bitmap files from this package.
      */
-    public void handlePackageUpdated(ShortcutService s, 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 ArraySet<String> getUsedBitmapFiles() {
+        final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
 
-        getPackageInfo().setVersionCode(newVersionCode);
-
-        boolean changed = false;
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
-
-            if (si.hasIconResource()) {
-                changed = true;
-                si.setTimestamp(s.injectCurrentTimeMillis());
+            if (si.getBitmapPath() != null) {
+                usedFiles.add(getFileName(si.getBitmapPath()));
             }
         }
+        return usedFiles;
+    }
+
+    private static String getFileName(@NonNull String path) {
+        final int sep = path.lastIndexOf(File.separatorChar);
+        if (sep == -1) {
+            return path;
+        } else {
+            return path.substring(sep + 1);
+        }
+    }
+
+    /**
+     * 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 boolean handlePackageAddedOrUpdated(boolean isNewApp) {
+        final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
+                getPackageName(), getPackageUserId());
+        if (pi == null) {
+            return false; // Shouldn't happen.
+        }
+
+        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 existing shortcuts, update timestamps if they have any resources.
+        if (!isNewApp) {
+            Resources publisherRes = null;
+
+            for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+                final ShortcutInfo si = mShortcuts.valueAt(i);
+
+                if (si.hasAnyResources()) {
+                    if (!si.isOriginallyFromManifest()) {
+                        if (publisherRes == null) {
+                            publisherRes = getPackageResources();
+                            if (publisherRes == null) {
+                                break; // Resources couldn't be loaded.
+                            }
+                        }
+
+                        // If this shortcut is not from a manifest, then update all resource IDs
+                        // from resource names.  (We don't allow resource strings for
+                        // non-manifest at the moment, but icons can still be resources.)
+                        si.lookupAndFillInResourceIds(publisherRes);
+                    }
+                    changed = true;
+                    si.setTimestamp(s.injectCurrentTimeMillis());
+                }
+            }
+        }
+
+        // (Re-)publish manifest shortcut.
+        changed |= publishManifestShortcuts(newManifestShortcutList);
+
+        if (newManifestShortcutList != null) {
+            changed |= pushOutExcessShortcuts();
+        }
+
         if (changed) {
             // This will send a notification to the launcher, and also save .
             s.packageShortcutsChanged(getPackageName(), getPackageUserId());
@@ -483,9 +689,388 @@
             // Still save the version code.
             s.scheduleSaveUser(getPackageUserId());
         }
+        return changed;
     }
 
-    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+    private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) {
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, String.format(
+                    "Package %s: publishing manifest shortcuts", getPackageName()));
+        }
+        boolean changed = false;
+
+        // Keep the previous IDs.
+        ArraySet<String> toDisableList = null;
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+
+            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;
+                }
+
+                // 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;
+    }
+
+    /**
+     * For each target activity, make sure # of dynamic + manifest shortcuts <= max.
+     * If too many, we'll remove the dynamic with the lowest ranks.
+     */
+    private boolean pushOutExcessShortcuts() {
+        final ShortcutService service = mShortcutUser.mService;
+        final int maxShortcuts = service.getMaxActivityShortcuts();
+
+        boolean changed = false;
+
+        final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
+                sortShortcutsToActivities();
+        for (int outer = all.size() - 1; outer >= 0; outer--) {
+            final ArrayList<ShortcutInfo> list = all.valueAt(outer);
+            if (list.size() <= maxShortcuts) {
+                continue;
+            }
+            // Sort by isManifestShortcut() and getRank().
+            Collections.sort(list, mShortcutTypeAndRankComparator);
+
+            // Keep [0 .. max), and remove (as dynamic) [max .. size)
+            for (int inner = list.size() - 1; inner >= maxShortcuts; inner--) {
+                final ShortcutInfo shortcut = list.get(inner);
+
+                if (shortcut.isManifestShortcut()) {
+                    // This shouldn't happen -- excess shortcuts should all be non-manifest.
+                    // But just in case.
+                    service.wtf("Found manifest shortcuts in excess list.");
+                    continue;
+                }
+                deleteDynamicWithId(shortcut.getId());
+            }
+        }
+        service.verifyStates();
+
+        return changed;
+    }
+
+    /**
+     * To sort by isManifestShortcut() and getRank(). i.e.  manifest shortcuts come before
+     * non-manifest shortcuts, then sort by rank.
+     *
+     * This is used to decide which dynamic shortcuts to remove when an upgraded version has more
+     * manifest shortcuts than before and as a result we need to remove some of the dynamic
+     * shortcuts.  We sort manifest + dynamic shortcuts by this order, and remove the ones with
+     * the last ones.
+     *
+     * (Note the number of manifest shortcuts is always <= the max number, because if there are
+     * more, ShortcutParser would ignore the rest.)
+     */
+    final Comparator<ShortcutInfo> mShortcutTypeAndRankComparator = (ShortcutInfo a,
+            ShortcutInfo b) -> {
+        if (a.isManifestShortcut() && !b.isManifestShortcut()) {
+            return -1;
+        }
+        if (!a.isManifestShortcut() && b.isManifestShortcut()) {
+            return 1;
+        }
+        return Integer.compare(a.getRank(), b.getRank());
+    };
+
+    /**
+     * Build a list of shortcuts for each target activity and return as a map. The result won't
+     * contain "floating" shortcuts because they don't belong on any activities.
+     */
+    private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
+        final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
+                = new ArrayMap<>();
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+            if (si.isFloating()) {
+                continue; // Ignore floating shortcuts, which are not tied to any activities.
+            }
+
+            final ComponentName activity = si.getActivity();
+
+            ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
+            if (list == null) {
+                list = new ArrayList<>();
+                activitiesToShortcuts.put(activity, list);
+            }
+            list.add(si);
+        }
+        return activitiesToShortcuts;
+    }
+
+    /** Used by {@link #enforceShortcutCountsBeforeOperation} */
+    private void incrementCountForActivity(ArrayMap<ComponentName, Integer> counts,
+            ComponentName cn, int increment) {
+        Integer oldValue = counts.get(cn);
+        if (oldValue == null) {
+            oldValue = 0;
+        }
+
+        counts.put(cn, oldValue + increment);
+    }
+
+    /**
+     * Called by
+     * {@link android.content.pm.ShortcutManager#setDynamicShortcuts},
+     * {@link android.content.pm.ShortcutManager#addDynamicShortcuts}, and
+     * {@link android.content.pm.ShortcutManager#updateShortcuts} before actually performing
+     * the operation to make sure the operation wouldn't result in the target activities having
+     * more than the allowed number of dynamic/manifest shortcuts.
+     *
+     * @param newList shortcut list passed to set, add or updateShortcuts().
+     * @param operation add, set or update.
+     * @throws IllegalArgumentException if the operation would result in going over the max
+     *                                  shortcut count for any activity.
+     */
+    public void enforceShortcutCountsBeforeOperation(List<ShortcutInfo> newList,
+            @ShortcutOperation int operation) {
+        final ShortcutService service = mShortcutUser.mService;
+
+        // Current # of dynamic / manifest shortcuts for each activity.
+        // (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
+        // anyway.)
+        final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo shortcut = mShortcuts.valueAt(i);
+
+            if (shortcut.isManifestShortcut()) {
+                incrementCountForActivity(counts, shortcut.getActivity(), 1);
+            } else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
+                incrementCountForActivity(counts, shortcut.getActivity(), 1);
+            }
+        }
+
+        for (int i = newList.size() - 1; i >= 0; i--) {
+            final ShortcutInfo newShortcut = newList.get(i);
+            final ComponentName newActivity = newShortcut.getActivity();
+            if (newActivity == null) {
+                if (operation != ShortcutService.OPERATION_UPDATE) {
+                    // This method may be called before validating shortcuts, so this may happen,
+                    // and is a caller side error.
+                    throw new NullPointerException("Activity must be provided");
+                }
+                continue; // Activity can be null for update.
+            }
+
+            final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
+            if (original == null) {
+                if (operation == ShortcutService.OPERATION_UPDATE) {
+                    continue; // When updating, ignore if there's no target.
+                }
+                // Add() or set(), and there's no existing shortcut with the same ID.  We're
+                // simply publishing (as opposed to updating) this shortcut, so just +1.
+                incrementCountForActivity(counts, newActivity, 1);
+                continue;
+            }
+            if (original.isFloating() && (operation == ShortcutService.OPERATION_UPDATE)) {
+                // Updating floating shortcuts doesn't affect the count, so ignore.
+                continue;
+            }
+
+            // If it's add() or update(), then need to decrement for the previous activity.
+            // Skip it for set() since it's already been taken care of by not counting the original
+            // dynamic shortcuts in the first loop.
+            if (operation != ShortcutService.OPERATION_SET) {
+                final ComponentName oldActivity = original.getActivity();
+                if (!original.isFloating()) {
+                    incrementCountForActivity(counts, oldActivity, -1);
+                }
+            }
+            incrementCountForActivity(counts, newActivity, 1);
+        }
+
+        // Then make sure none of the activities have more than the max number of shortcuts.
+        for (int i = counts.size() - 1; i >= 0; i--) {
+            service.enforceMaxActivityShortcuts(counts.valueAt(i));
+        }
+    }
+
+    /**
+     * For all the text fields, refresh the string values if they're from resources.
+     */
+    public void resolveResourceStrings() {
+        final ShortcutService s = mShortcutUser.mService;
+        boolean changed = false;
+
+        Resources publisherRes = null;
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+
+            if (si.hasStringResources()) {
+                changed = true;
+
+                if (publisherRes == null) {
+                    publisherRes = getPackageResources();
+                    if (publisherRes == null) {
+                        break; // Resources couldn't be loaded.
+                    }
+                }
+
+                si.resolveResourceStrings(publisherRes);
+                si.setTimestamp(s.injectCurrentTimeMillis());
+            }
+        }
+        if (changed) {
+            s.scheduleSaveUser(getPackageUserId());
+        }
+    }
+
+    /** Clears the implicit ranks for all shortcuts. */
+    public void clearAllImplicitRanks() {
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+            si.clearImplicitRankAndRankChangedFlag();
+        }
+    }
+
+    /**
+     * Used to sort shortcuts for rank auto-adjusting.
+     */
+    final Comparator<ShortcutInfo> mShortcutRankComparator = (ShortcutInfo a, ShortcutInfo b) -> {
+        // First, sort by rank.
+        int ret = Integer.compare(a.getRank(), b.getRank());
+        if (ret != 0) {
+            return ret;
+        }
+        // When ranks are tie, then prioritize the ones that have just been assigned new ranks.
+        // e.g. when there are 3 shortcuts, "s1" "s2" and "s3" with rank 0, 1, 2 respectively,
+        // adding a shortcut "s4" with rank 1 will "insert" it between "s1" and "s2", because
+        // "s2" and "s4" have the same rank 1 but s4 has isRankChanged() set.
+        // Similarly, updating s3's rank to 1 will insert it between s1 and s2.
+        if (a.isRankChanged() != b.isRankChanged()) {
+            return a.isRankChanged() ? -1 : 1;
+        }
+        // If they're still tie, sort by implicit rank -- i.e. preserve the order in which
+        // they're passed to the API.
+        ret = Integer.compare(a.getImplicitRank(), b.getImplicitRank());
+        if (ret != 0) {
+            return ret;
+        }
+        // If they're stil tie, just sort by their IDs.
+        // This may happen with updateShortcuts() -- see
+        // the testUpdateShortcuts_noManifestShortcuts() test.
+        return a.getId().compareTo(b.getId());
+    };
+
+    /**
+     * Re-calculate the ranks for all shortcuts.
+     */
+    public void adjustRanks() {
+        final ShortcutService s = mShortcutUser.mService;
+        final long now = s.injectCurrentTimeMillis();
+
+        // First, clear ranks for floating shortcuts.
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+            if (si.isFloating()) {
+                if (si.getRank() != 0) {
+                    si.setTimestamp(now);
+                    si.setRank(0);
+                }
+            }
+        }
+
+        // Then adjust ranks.  Ranks are unique for each activity, so we first need to sort
+        // shortcuts to each activity.
+        // Then sort the shortcuts within each activity with mShortcutRankComparator, and
+        // assign ranks from 0.
+        final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
+                sortShortcutsToActivities();
+        for (int outer = all.size() - 1; outer >= 0; outer--) { // For each activity.
+            final ArrayList<ShortcutInfo> list = all.valueAt(outer);
+
+            // Sort by ranks and other signals.
+            Collections.sort(list, mShortcutRankComparator);
+
+            int rank = 0;
+
+            final int size = list.size();
+            for (int i = 0; i < size; i++) {
+                final ShortcutInfo si = list.get(i);
+                if (si.isManifestShortcut()) {
+                    // Don't adjust ranks for manifest shortcuts.
+                    continue;
+                }
+                // At this point, it must be dynamic.
+                if (!si.isDynamic()) {
+                    s.wtf("Non-dynamic shortcut found.");
+                    continue;
+                }
+                final int thisRank = rank++;
+                if (si.getRank() != thisRank) {
+                    si.setTimestamp(now);
+                    si.setRank(thisRank);
+                }
+            }
+        }
+    }
+
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         pw.println();
 
         pw.print(prefix);
@@ -498,7 +1083,7 @@
         pw.print(prefix);
         pw.print("  ");
         pw.print("Calls: ");
-        pw.print(getApiCallCount(s));
+        pw.print(getApiCallCount());
         pw.println();
 
         // getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
@@ -514,10 +1099,10 @@
         pw.print("Last reset: [");
         pw.print(mLastResetTime);
         pw.print("] ");
-        pw.print(s.formatTime(mLastResetTime));
+        pw.print(ShortcutService.formatTime(mLastResetTime));
         pw.println();
 
-        getPackageInfo().dump(s, pw, prefix + "  ");
+        getPackageInfo().dump(pw, prefix + "  ");
         pw.println();
 
         pw.print(prefix);
@@ -545,7 +1130,7 @@
         pw.print("Total bitmap size: ");
         pw.print(totalBitmapSize);
         pw.print(" (");
-        pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
+        pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
         pw.println(")");
     }
 
@@ -561,7 +1146,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);
@@ -583,12 +1167,20 @@
         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());
+        ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
         ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
+        ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
+        ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
+        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
+        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
+                si.getDisabledMessageResourceId());
+        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
+                si.getDisabledMessageResName());
         ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
-        ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
         ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
                 si.getLastChangedTimestamp());
         if (forBackup) {
@@ -598,8 +1190,13 @@
                             ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
                             | ShortcutInfo.FLAG_DYNAMIC));
         } else {
+            // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
+            // as dynamic.
+            ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
+
             ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
-            ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId());
+            ShortcutService.writeAttr(out, ATTR_ICON_RES_ID, si.getIconResourceId());
+            ShortcutService.writeAttr(out, ATTR_ICON_RES_NAME, si.getIconResName());
             ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
         }
 
@@ -627,11 +1224,9 @@
         final String packageName = ShortcutService.parseStringAttribute(parser,
                 ATTR_NAME);
 
-        final ShortcutPackage ret = new ShortcutPackage(s, shortcutUser,
+        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 =
@@ -671,14 +1266,22 @@
         ComponentName activityComponent;
         // Icon icon;
         String title;
+        int titleResId;
+        String titleResName;
         String text;
+        int textResId;
+        String textResName;
+        String disabledMessage;
+        int disabledMessageResId;
+        String disabledMessageResName;
         Intent intent;
         PersistableBundle intentPersistableExtras = null;
-        int weight;
+        int rank;
         PersistableBundle extras = null;
         long lastChangedTimestamp;
         int flags;
-        int iconRes;
+        int iconResId;
+        String iconResName;
         String bitmapPath;
         ArraySet<String> categories = null;
 
@@ -686,12 +1289,22 @@
         activityComponent = ShortcutService.parseComponentNameAttribute(parser,
                 ATTR_ACTIVITY);
         title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
+        titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
+        titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
         text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
+        textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
+        textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
+        disabledMessage = ShortcutService.parseStringAttribute(parser, ATTR_DISABLED_MESSAGE);
+        disabledMessageResId = ShortcutService.parseIntAttribute(parser,
+                ATTR_DISABLED_MESSAGE_RES_ID);
+        disabledMessageResName = ShortcutService.parseStringAttribute(parser,
+                ATTR_DISABLED_MESSAGE_RES_NAME);
         intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
-        weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
+        rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
         lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
         flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
-        iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
+        iconResId = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES_ID);
+        iconResName = ShortcutService.parseStringAttribute(parser, ATTR_ICON_RES_NAME);
         bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
 
         final int outerDepth = parser.getDepth();
@@ -733,14 +1346,97 @@
         }
 
         return new ShortcutInfo(
-                userId, id, packageName, activityComponent, /* icon =*/ null, title, text,
+                userId, id, packageName, activityComponent, /* icon =*/ null,
+                title, titleResId, titleResName, text, textResId, textResName,
+                disabledMessage, disabledMessageResId, disabledMessageResName,
                 categories, intent,
-                intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
-                iconRes, bitmapPath);
+                intentPersistableExtras, rank, extras, lastChangedTimestamp, flags,
+                iconResId, iconResName, bitmapPath);
     }
 
     @VisibleForTesting
     List<ShortcutInfo> getAllShortcutsForTest() {
         return new ArrayList<>(mShortcuts.values());
     }
+
+    @Override
+    public void verifyStates() {
+        super.verifyStates();
+
+        boolean failed = false;
+
+        final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
+                sortShortcutsToActivities();
+
+        // Make sure each activity won't have more than max shortcuts.
+        for (int outer = all.size() - 1; outer >= 0; outer--) {
+            final ArrayList<ShortcutInfo> list = all.valueAt(outer);
+            if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
+                        + " has " + all.valueAt(outer).size() + " shortcuts.");
+            }
+
+            // Sort by rank.
+            Collections.sort(list, (a, b) -> Integer.compare(a.getRank(), b.getRank()));
+
+            // Split into two arrays for each kind.
+            final ArrayList<ShortcutInfo> dynamicList = new ArrayList<>(list);
+            dynamicList.removeIf((si) -> !si.isDynamic());
+
+            final ArrayList<ShortcutInfo> manifestList = new ArrayList<>(list);
+            dynamicList.removeIf((si) -> !si.isManifestShortcut());
+
+            verifyRanksSequential(dynamicList);
+            verifyRanksSequential(manifestList);
+        }
+
+        // Verify each shortcut's status.
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+            if (!(si.isManifestShortcut() || si.isDynamic() || si.isPinned())) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " is not manifest, dynamic or pinned.");
+            }
+            if (si.isManifestShortcut() && si.isDynamic()) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " is both dynamic and manifest at the same time.");
+            }
+            if (si.getActivity() == null) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " has null activity.");
+            }
+            if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " is not floating, but is disabled.");
+            }
+            if (si.isFloating() && si.getRank() != 0) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " is floating, but has rank=" + si.getRank());
+            }
+        }
+
+        if (failed) {
+            throw new IllegalStateException("See logcat for errors");
+        }
+    }
+
+    private boolean verifyRanksSequential(List<ShortcutInfo> list) {
+        boolean failed = false;
+
+        for (int i = 0; i < list.size(); i++) {
+            final ShortcutInfo si = list.get(i);
+            if (si.getRank() != i) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " rank=" + si.getRank() + " but expected to be "+ i);
+            }
+        }
+        return failed;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 74969f0..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,11 +205,12 @@
 
         // Successfully loaded; replace the feilds.
         mVersionCode = versionCode;
+        mLastUpdateTime = lastUpdateTime;
         mIsShadow = shadow;
         mSigHashes = hashes;
     }
 
-    public void dump(ShortcutService s, PrintWriter pw, String prefix) {
+    public void dump(PrintWriter pw, String prefix) {
         pw.println();
 
         pw.print(prefix);
@@ -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 6fbdb82..757dd19 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;
 
@@ -69,18 +72,20 @@
         return mPackageInfo;
     }
 
-    public void refreshPackageInfoAndSave(ShortcutService s) {
+    public void refreshPackageInfoAndSave() {
         if (mPackageInfo.isShadow()) {
             return; // Don't refresh for shadow user.
         }
+        final ShortcutService s = mShortcutUser.mService;
         mPackageInfo.refresh(s, this);
         s.scheduleSaveUser(getOwnerUserId());
     }
 
-    public void attemptToRestoreIfNeededAndSave(ShortcutService s) {
+    public void attemptToRestoreIfNeededAndSave() {
         if (!mPackageInfo.isShadow()) {
             return; // Already installed, nothing to do.
         }
+        final ShortcutService s = mShortcutUser.mService;
         if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
             if (ShortcutService.DEBUG) {
                 Slog.d(TAG, String.format("Package still not installed: %s user=%d",
@@ -91,14 +96,14 @@
         if (!mPackageInfo.hasSignatures()) {
             s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
                     + " but signatures not found in the restore data.");
-            onRestoreBlocked(s);
+            onRestoreBlocked();
             return;
         }
 
         final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
         if (!mPackageInfo.canRestoreTo(s, pi)) {
             // Package is now installed, but can't restore.  Let the subclass do the cleanup.
-            onRestoreBlocked(s);
+            onRestoreBlocked();
             return;
         }
         if (ShortcutService.DEBUG) {
@@ -106,7 +111,7 @@
                     mPackageUserId, getOwnerUserId()));
         }
 
-        onRestored(s);
+        onRestored();
 
         // Now the package is not shadow.
         mPackageInfo.setShadow(false);
@@ -118,13 +123,19 @@
      * Called when the new package can't be restored because it has a lower version number
      * or different signatures.
      */
-    protected abstract void onRestoreBlocked(ShortcutService s);
+    protected abstract void onRestoreBlocked();
 
     /**
      * Called when the new package is successfully restored.
      */
-    protected abstract void onRestored(ShortcutService s);
+    protected abstract void onRestored();
 
     public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException;
+
+    /**
+     * Verify various internal states.
+     */
+    public void verifyStates() {
+    }
 }
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..ec19927
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -0,0 +1,253 @@
+/*
+ * 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;
+
+            int rank = 0;
+            final int maxShortcuts = service.getMaxActivityShortcuts();
+            int numShortcuts = 0;
+
+            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, rank);
+                    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 (numShortcuts >= maxShortcuts) {
+                            Slog.w(TAG, "More than " + maxShortcuts + " shortcuts found for "
+                                    + activityInfo.getComponentName() + ", ignoring the rest.");
+                            return result;
+                        }
+
+                        if (result == null) {
+                            result = new ArrayList<>();
+                        }
+                        result.add(si);
+                        numShortcuts++;
+                        rank++;
+                    }
+                    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, int rank) {
+        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 iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0);
+            final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0);
+            final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
+            final int disabledMessageResId = sa.getResourceId(
+                    R.styleable.Shortcut_shortcutDisabledMessage, 0);
+            final String categories = sa.getString(R.styleable.Shortcut_shortcutCategories);
+            String intentAction = sa.getString(R.styleable.Shortcut_shortcutIntentAction);
+            final String intentData = sa.getString(R.styleable.Shortcut_shortcutIntentData);
+
+            if (TextUtils.isEmpty(id)) {
+                Slog.w(TAG, "Shortcut ID must be provided. activity=" + activity);
+                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);
+
+        // Note we don't need to set resource names here yet.  They'll be set when they're about
+        // to be published.
+        return new ShortcutInfo(
+                userId,
+                id,
+                packageName,
+                activityComponent,
+                null, // icon
+                null, // title string
+                titleResId,
+                null, // title res name
+                null, // text string
+                textResId,
+                null, // text res name
+                null, // disabled message string
+                disabledMessageResId,
+                null, // disabled message res name
+                categories,
+                intent,
+                null, // intent extras
+                rank,
+                null, // extras
+                service.injectCurrentTimeMillis(),
+                flags,
+                iconResId,
+                null, // icon res name
+                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 8268776..1619168 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.pm;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -22,9 +23,11 @@
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
 import android.app.IUidObserver;
+import android.app.usage.UsageStatsManagerInternal;
 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;
@@ -32,12 +35,15 @@
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.Canvas;
@@ -63,6 +69,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.KeyValueListParser;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -74,7 +81,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
@@ -100,33 +106,49 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 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;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
  * TODO:
+ * - Deal with the async nature of PACKAGE_ADD.  Basically when a publisher does anything after
+ *   it's upgraded, the manager should make sure the upgrade process has been executed.
+ *
+ * - 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.  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.
  *
- * - Clear data -> remove all dynamic?  but not the pinned?
- *
- * - Scan and remove orphan bitmaps (just in case).
- *
  * - Detect when already registered instances are passed to APIs again, which might break
  *   internal bitmap handling.
  *
  * - Add more call stats.
+ *
+ * - Rename mMaxDynamicShortcuts, because it includes manifest shortcuts too.
  */
 public class ShortcutService extends IShortcutService.Stub {
     static final String TAG = "ShortcutService";
 
-    public static final boolean FEATURE_ENABLED = false;
-
     static final boolean DEBUG = false; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
@@ -200,7 +222,7 @@
         String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
 
         /**
-         * Key name for the max dynamic shortcuts per app. (int)
+         * Key name for the max dynamic shortcuts per activity. (int)
          */
         String KEY_MAX_SHORTCUTS = "max_shortcuts";
 
@@ -261,6 +283,7 @@
     private final IPackageManager mIPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
     private final UserManager mUserManager;
+    private final UsageStatsManagerInternal mUsageStatsManagerInternal;
 
     @GuardedBy("mLock")
     final SparseIntArray mUidState = new SparseIntArray();
@@ -280,6 +303,8 @@
      */
     private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
 
+    private final AtomicBoolean mBootCompleted = new AtomicBoolean();
+
     private static final int PACKAGE_MATCH_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE
             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -293,8 +318,14 @@
         int GET_PACKAGE_INFO_WITH_SIG = 2;
         int GET_APPLICATION_INFO = 3;
         int LAUNCHER_PERMISSION_CHECK = 4;
+        int CLEANUP_DANGLING_BITMAPS = 5;
+        int GET_ACTIVITIES_WITH_METADATA = 6;
+        int GET_INSTALLED_PACKAGES = 7;
+        int CHECK_PACKAGE_CHANGES = 8;
+        int GET_APPLICATION_RESOURCES = 9;
+        int RESOURCE_NAME_LOOKUP = 10;
 
-        int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
+        int COUNT = RESOURCE_NAME_LOOKUP + 1;
     }
 
     final Object mStatLock = new Object();
@@ -308,6 +339,19 @@
     private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
             ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
 
+    static final int OPERATION_SET = 0;
+    static final int OPERATION_ADD = 1;
+    static final int OPERATION_UPDATE = 2;
+
+    /** @hide */
+    @IntDef(value = {
+            OPERATION_SET,
+            OPERATION_ADD,
+            OPERATION_UPDATE
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ShortcutOperation {}
+
     public ShortcutService(Context context) {
         this(context, BackgroundThread.get().getLooper());
     }
@@ -318,12 +362,12 @@
         LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
         mHandler = new Handler(looper);
         mIPackageManager = AppGlobals.getPackageManager();
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mUserManager = context.getSystemService(UserManager.class);
+        mPackageManagerInternal = Preconditions.checkNotNull(
+                LocalServices.getService(PackageManagerInternal.class));
+        mUserManager = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
+        mUsageStatsManagerInternal = Preconditions.checkNotNull(
+                LocalServices.getService(UsageStatsManagerInternal.class));
 
-        if (!FEATURE_ENABLED) {
-            return;
-        }
         mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
 
         injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
@@ -333,7 +377,7 @@
     void logDurationStat(int statId, long start) {
         synchronized (mStatLock) {
             mCountStats[statId]++;
-            mDurationStats[statId] += (System.currentTimeMillis() - start);
+            mDurationStats[statId] += (injectElapsedRealtime() - start);
         }
     }
 
@@ -424,7 +468,6 @@
 
     /** lifecycle event */
     void onBootPhase(int phase) {
-        // We want to call initialize() to initialize the configurations, so we don't disable this.
         if (DEBUG) {
             Slog.d(TAG, "onBootPhase: " + phase);
         }
@@ -432,13 +475,16 @@
             case SystemService.PHASE_LOCK_SETTINGS_READY:
                 initialize();
                 break;
+            case SystemService.PHASE_BOOT_COMPLETED:
+                mBootCompleted.set(true);
+                break;
         }
     }
 
     /** lifecycle event */
     void handleUnlockUser(int userId) {
-        if (!FEATURE_ENABLED) {
-            return;
+        if (DEBUG) {
+            Slog.d(TAG, "handleUnlockUser: user=" + userId);
         }
         synchronized (mLock) {
             // Preload
@@ -450,9 +496,6 @@
 
     /** lifecycle event */
     void handleCleanupUser(int userId) {
-        if (!FEATURE_ENABLED) {
-            return;
-        }
         synchronized (mLock) {
             unloadUserLocked(userId);
         }
@@ -641,10 +684,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 {
@@ -782,7 +825,7 @@
         out.setOutput(bos, StandardCharsets.UTF_8.name());
         out.startDocument(null, true);
 
-        getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
+        getUserShortcutsLocked(userId).saveToXml(out, forBackup);
 
         out.endDocument();
 
@@ -816,7 +859,9 @@
             return null;
         }
         try {
-            return loadUserInternal(userId, in, /* forBackup= */ false);
+            final ShortcutUser ret =  loadUserInternal(userId, in, /* forBackup= */ false);
+            cleanupDanglingBitmapDirectoriesLocked(userId, ret);
+            return ret;
         } catch (IOException|XmlPullParserException e) {
             Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
             return null;
@@ -958,7 +1003,7 @@
         if (userPackages == null) {
             userPackages = loadUserLocked(userId);
             if (userPackages == null) {
-                userPackages = new ShortcutUser(userId);
+                userPackages = new ShortcutUser(this, userId);
             }
             mUsers.put(userId, userPackages);
         }
@@ -976,7 +1021,7 @@
     @NonNull
     ShortcutPackage getPackageShortcutsLocked(
             @NonNull String packageName, @UserIdInt int userId) {
-        return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
+        return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
     }
 
     @GuardedBy("mLock")
@@ -985,7 +1030,7 @@
             @NonNull String packageName, @UserIdInt int ownerUserId,
             @UserIdInt int launcherUserId) {
         return getUserShortcutsLocked(ownerUserId)
-                .getLauncherShortcuts(this, packageName, launcherUserId);
+                .getLauncherShortcuts(packageName, launcherUserId);
     }
 
     // === Caller validation ===
@@ -1013,6 +1058,57 @@
         }
     }
 
+    private void cleanupDanglingBitmapDirectoriesLocked(
+            @UserIdInt int userId, @NonNull ShortcutUser user) {
+        if (DEBUG) {
+            Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
+        }
+        final long start = injectElapsedRealtime();
+
+        final File bitmapDir = getUserBitmapFilePath(userId);
+        final File[] children = bitmapDir.listFiles();
+        if (children == null) {
+            return;
+        }
+        for (File child : children) {
+            if (!child.isDirectory()) {
+                continue;
+            }
+            final String packageName = child.getName();
+            if (DEBUG) {
+                Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName);
+            }
+            if (!user.hasPackage(packageName)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Removing dangling bitmap directory: " + packageName);
+                }
+                cleanupBitmapsForPackage(userId, packageName);
+            } else {
+                cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
+            }
+        }
+        logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
+    }
+
+    private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
+            @NonNull String packageName, @NonNull File path) {
+        final ArraySet<String> usedFiles =
+                user.getPackageShortcuts(packageName).getUsedBitmapFiles();
+
+        for (File child : path.listFiles()) {
+            if (!child.isFile()) {
+                continue;
+            }
+            final String name = child.getName();
+            if (!usedFiles.contains(name)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
+                }
+                child.delete();
+            }
+        }
+    }
+
     @VisibleForTesting
     static class FileOutputStreamWithPath extends FileOutputStream {
         private final File mFile;
@@ -1036,7 +1132,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()) {
@@ -1075,7 +1171,6 @@
             }
 
             Bitmap bitmap;
-            Bitmap bitmapToRecycle = null;
             try {
                 switch (icon.getType()) {
                     case Icon.TYPE_RESOURCE: {
@@ -1127,9 +1222,6 @@
                     }
                 }
             } finally {
-                if (bitmapToRecycle != null) {
-                    bitmapToRecycle.recycle();
-                }
                 // Once saved, we won't use the original icon information, so null it out.
                 shortcut.clearIcon();
             }
@@ -1142,7 +1234,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");
         }
@@ -1179,6 +1271,24 @@
         return scaledBitmap;
     }
 
+    /**
+     * For a shortcut, update all resource names from resource IDs, and also update all
+     * resource-based strings.
+     */
+    void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) {
+        final Resources publisherRes = injectGetResourcesForApplicationAsUser(
+                si.getPackage(), si.getUserId());
+        if (publisherRes != null) {
+            final long start = injectElapsedRealtime();
+            try {
+                si.lookupAndFillInResourceNames(publisherRes);
+            } finally {
+                logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start);
+            }
+            si.resolveResourceStrings(publisherRes);
+        }
+    }
+
     // === Caller validation ===
 
     private boolean isCallerSystem() {
@@ -1241,20 +1351,29 @@
         throw new SecurityException("Calling package name mismatch");
     }
 
-    void postToHandler(Runnable r) {
+    // Overridden in unit tests to execute r synchronously.
+    void injectPostToHandler(Runnable r) {
         mHandler.post(r);
     }
 
     /**
-     * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
+     * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
+     * {@link #getMaxActivityShortcuts()}.
      */
-    void enforceMaxDynamicShortcuts(int numShortcuts) {
+    void enforceMaxActivityShortcuts(int numShortcuts) {
         if (numShortcuts > mMaxDynamicShortcuts) {
             throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
         }
     }
 
     /**
+     * Return the max number of dynamic + manifest shortcuts for each launcher icon.
+     */
+    int getMaxActivityShortcuts() {
+        return mMaxDynamicShortcuts;
+    }
+
+    /**
      * - Sends a notification to LauncherApps
      * - Write to file
      */
@@ -1268,10 +1387,15 @@
     }
 
     private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
-        if (!mUserManager.isUserRunning(userId)) {
-            return;
+        final long token = injectClearCallingIdentity();
+        try {
+            if (!mUserManager.isUserRunning(userId)) {
+                return;
+            }
+        } finally {
+            injectRestoreCallingIdentity(token);
         }
-        postToHandler(() -> {
+        injectPostToHandler(() -> {
             final ArrayList<ShortcutChangeListener> copy;
             synchronized (mLock) {
                 copy = new ArrayList<>(mListeners);
@@ -1294,10 +1418,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");
         }
 
@@ -1340,7 +1463,7 @@
         }
     }
 
-    private static void validateForXml(String s) {
+    private static void validateForXml(CharSequence s) {
         if (TextUtils.isEmpty(s)) {
             return;
         }
@@ -1355,6 +1478,12 @@
         return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
     }
 
+    private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
+        for (int i = shortcuts.size() - 1; i >= 0; i--) {
+            shortcuts.get(i).setImplicitRank(i);
+        }
+    }
+
     // === APIs ===
 
     @Override
@@ -1368,27 +1497,35 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
+            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
+
             // Throttling.
-            if (!ps.tryApiCall(this)) {
+            if (!ps.tryApiCall()) {
                 return false;
             }
-            enforceMaxDynamicShortcuts(size);
 
-            // Validate the shortcuts.
-            for (int i = 0; i < size; i++) {
-                fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
-            }
+            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
+            ps.clearAllImplicitRanks();
+            assignImplicitRanks(newShortcuts);
 
             // First, remove all un-pinned; dynamic shortcuts
-            ps.deleteAllDynamicShortcuts(this);
+            ps.deleteAllDynamicShortcuts();
 
             // 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(this, newShortcut);
+                ps.addOrUpdateDynamicShortcut(newShortcut);
             }
+
+            // Lastly, adjust the ranks.
+            ps.adjustRanks();
         }
         packageShortcutsChanged(packageName, userId);
+
+        verifyStates();
+
         return true;
     }
 
@@ -1403,32 +1540,72 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
+            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
+
             // Throttling.
-            if (!ps.tryApiCall(this)) {
+            if (!ps.tryApiCall()) {
                 return false;
             }
 
+            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
+            ps.clearAllImplicitRanks();
+            assignImplicitRanks(newShortcuts);
+
             for (int i = 0; i < size; i++) {
                 final ShortcutInfo source = newShortcuts.get(i);
                 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
 
                 final ShortcutInfo target = ps.findShortcutById(source.getId());
-                if (target != null) {
-                    final boolean replacingIcon = (source.getIcon() != null);
-                    if (replacingIcon) {
-                        removeIcon(userId, target);
-                    }
+                if (target == null) {
+                    continue;
+                }
 
-                    target.copyNonNullFieldsFrom(source);
+                if (target.isEnabled() != source.isEnabled()) {
+                    Slog.w(TAG,
+                            "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
+                }
 
-                    if (replacingIcon) {
-                        saveIconAndFixUpShortcut(userId, target);
-                    }
+                // When updating the rank, we need to insert between existing ranks, so set
+                // this setRankChanged, and also copy the implicit rank fo adjustRanks().
+                if (source.hasRank()) {
+                    target.setRankChanged();
+                    target.setImplicitRank(source.getImplicitRank());
+                }
+
+                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 "updatable with?" check too.
+                target.copyNonNullFieldsFrom(source);
+                target.setTimestamp(injectCurrentTimeMillis());
+
+                if (replacingIcon) {
+                    saveIconAndFixUpShortcut(userId, target);
+                }
+
+                // When we're updating any resource related fields, re-extract the res names and
+                // the values.
+                if (replacingIcon || source.hasStringResources()) {
+                    fixUpShortcutResourceNamesAndValues(target);
                 }
             }
+
+            // Lastly, adjust the ranks.
+            ps.adjustRanks();
         }
         packageShortcutsChanged(packageName, userId);
 
+        verifyStates();
+
         return true;
     }
 
@@ -1443,8 +1620,16 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
+            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
+
+            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
+            ps.clearAllImplicitRanks();
+            assignImplicitRanks(newShortcuts);
+
             // Throttling.
-            if (!ps.tryApiCall(this)) {
+            if (!ps.tryApiCall()) {
                 return false;
             }
             for (int i = 0; i < size; i++) {
@@ -1453,13 +1638,66 @@
                 // Validate the shortcut.
                 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
 
+                // When ranks are changing, we need to insert between ranks, so set the
+                // "rank changed" flag.
+                newShortcut.setRankChanged();
+
                 // Add it.
-                ps.addDynamicShortcut(this, newShortcut);
+                ps.addOrUpdateDynamicShortcut(newShortcut);
+            }
+
+            // Lastly, adjust the ranks.
+            ps.adjustRanks();
+        }
+        packageShortcutsChanged(packageName, userId);
+
+        verifyStates();
+
+        return true;
+    }
+
+    @Override
+    public void disableShortcuts(String packageName, List shortcutIds,
+            String disabledMessage, int disabledMessageResId, @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.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
+                        disabledMessage, disabledMessageResId,
+                        /* overrideImmutable=*/ false);
+            }
+
+            // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
+            ps.adjustRanks();
+        }
+        packageShortcutsChanged(packageName, userId);
+
+        verifyStates();
+    }
+
+    @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);
 
-        return true;
+        verifyStates();
     }
 
     @Override
@@ -1469,12 +1707,21 @@
         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(this,
+                ps.deleteDynamicWithId(
                         Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
             }
+
+            // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
+            ps.adjustRanks();
         }
         packageShortcutsChanged(packageName, userId);
+
+        verifyStates();
     }
 
     @Override
@@ -1482,9 +1729,11 @@
         verifyCaller(packageName, userId);
 
         synchronized (mLock) {
-            getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
+            getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
         }
         packageShortcutsChanged(packageName, userId);
+
+        verifyStates();
     }
 
     @Override
@@ -1499,6 +1748,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);
@@ -1514,7 +1774,7 @@
 
         final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
-        getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
+        getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags);
 
         return new ParceledListSlice<>(ret);
     }
@@ -1533,7 +1793,7 @@
 
         synchronized (mLock) {
             return mMaxUpdatesPerInterval
-                    - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
+                    - getPackageShortcutsLocked(packageName, userId).getApiCallCount();
         }
     }
 
@@ -1547,7 +1807,7 @@
     }
 
     @Override
-    public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
+    public int getIconMaxDimensions(String packageName, int userId) {
         verifyCaller(packageName, userId);
 
         synchronized (mLock) {
@@ -1555,6 +1815,34 @@
         }
     }
 
+    @Override
+    public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
+        verifyCaller(packageName, userId);
+
+        Preconditions.checkNotNull(shortcutId);
+
+        if (DEBUG) {
+            Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
+                    shortcutId, packageName, userId));
+        }
+
+        synchronized (mLock) {
+            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+            if (ps.findShortcutById(shortcutId) == null) {
+                Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
+                        packageName, shortcutId));
+                return;
+            }
+        }
+
+        final long token = injectClearCallingIdentity();
+        try {
+            mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
+        } finally {
+            injectRestoreCallingIdentity(token);
+        }
+    }
+
     /**
      * Reset all throttling, for developer options and command line.  Only system/shell can call it.
      */
@@ -1608,14 +1896,14 @@
     @VisibleForTesting
     boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
         synchronized (mLock) {
-            final long start = System.currentTimeMillis();
+            final long start = injectElapsedRealtime();
 
             final ShortcutUser user = getUserShortcutsLocked(userId);
 
             final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
 
             // Default launcher from package manager.
-            final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
+            final long startGetHomeActivitiesAsUser = injectElapsedRealtime();
             final ComponentName defaultLauncher = injectPackageManagerInternal()
                     .getHomeActivitiesAsUser(allHomeCandidates, userId);
             logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
@@ -1627,7 +1915,7 @@
                     Slog.v(TAG, "Default launcher from PM: " + detected);
                 }
             } else {
-                detected = user.getLauncherComponent();
+                detected = user.getDefaultLauncherComponent();
 
                 // TODO: Make sure it's still enabled.
                 if (DEBUG) {
@@ -1667,7 +1955,7 @@
                 if (DEBUG) {
                     Slog.v(TAG, "Detected launcher: " + detected);
                 }
-                user.setLauncherComponent(this, detected);
+                user.setDefaultLauncherComponent(detected);
                 return detected.getPackageName().equals(callingPackage);
             } else {
                 // Default launcher not found.
@@ -1701,7 +1989,7 @@
 
         // First, remove the package from the package list (if the package is a publisher).
         if (packageUserId == owningUserId) {
-            if (user.removePackage(this, packageName) != null) {
+            if (user.removePackage(packageName) != null) {
                 doNotify = true;
             }
         }
@@ -1714,7 +2002,7 @@
 
         // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
         // step.  Remove them too.
-        user.forAllPackages(p -> p.refreshPinnedFlags(this));
+        user.forAllPackages(p -> p.refreshPinnedFlags());
 
         scheduleSaveUser(owningUserId);
 
@@ -1740,17 +2028,17 @@
                 @Nullable ComponentName componentName,
                 int queryFlags, int userId) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
-            final int cloneFlag =
-                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
-                            ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
-                            : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
+            final boolean cloneKeyFieldOnly =
+                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
+            final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
+                    : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
             if (packageName == null) {
                 shortcutIds = null; // LauncherAppsService already threw for it though.
             }
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
-                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+                        .attemptToRestoreIfNeededAndSave();
 
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
@@ -1775,7 +2063,13 @@
             final ArraySet<String> ids = shortcutIds == null ? null
                     : new ArraySet<>(shortcutIds);
 
-            getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
+            final ShortcutPackage p = getUserShortcutsLocked(userId)
+                    .getPackageShortcutsIfExists(packageName);
+            if (p == null) {
+                return; // No need to instantiate ShortcutPackage.
+            }
+
+            p.findAll(ret,
                     (ShortcutInfo si) -> {
                         if (si.getLastChangedTimestamp() < changedSince) {
                             return false;
@@ -1783,17 +2077,25 @@
                         if (ids != null && !ids.contains(si.getId())) {
                             return false;
                         }
-                        if (componentName != null
-                                && !componentName.equals(si.getActivityComponent())) {
-                            return false;
+                        if (componentName != null) {
+                            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);
         }
 
@@ -1805,7 +2107,7 @@
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
-                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+                        .attemptToRestoreIfNeededAndSave();
 
                 final ShortcutInfo si = getShortcutInfoLocked(
                         launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1819,9 +2121,14 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
 
+            final ShortcutPackage p = getUserShortcutsLocked(userId)
+                    .getPackageShortcutsIfExists(packageName);
+            if (p == null) {
+                return null;
+            }
+
             final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
-            getPackageShortcutsLocked(packageName, userId).findAll(
-                    ShortcutService.this, list,
+            p.findAll(list,
                     (ShortcutInfo si) -> shortcutId.equals(si.getId()),
                     /* clone flags=*/ 0, callingPackage, launcherUserId);
             return list.size() == 0 ? null : list.get(0);
@@ -1838,12 +2145,13 @@
             synchronized (mLock) {
                 final ShortcutLauncher launcher =
                         getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
-                launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
+                launcher.attemptToRestoreIfNeededAndSave();
 
-                launcher.pinShortcuts(
-                        ShortcutService.this, userId, packageName, shortcutIds);
+                launcher.pinShortcuts(userId, packageName, shortcutIds);
             }
             packageShortcutsChanged(packageName, userId);
+
+            verifyStates();
         }
 
         @Override
@@ -1856,13 +2164,13 @@
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
-                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+                        .attemptToRestoreIfNeededAndSave();
 
                 // Make sure the shortcut is actually visible to the launcher.
                 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();
@@ -1885,10 +2193,15 @@
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
-                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+                        .attemptToRestoreIfNeededAndSave();
 
-                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
-                        packageName, userId).findShortcutById(shortcutId);
+                final ShortcutPackage p = getUserShortcutsLocked(userId)
+                        .getPackageShortcutsIfExists(packageName);
+                if (p == null) {
+                    return 0;
+                }
+
+                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
                 return (shortcutInfo != null && shortcutInfo.hasIconResource())
                         ? shortcutInfo.getIconResourceId() : 0;
             }
@@ -1904,10 +2217,15 @@
 
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
-                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+                        .attemptToRestoreIfNeededAndSave();
 
-                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
-                        packageName, userId).findShortcutById(shortcutId);
+                final ShortcutPackage p = getUserShortcutsLocked(userId)
+                        .getPackageShortcutsIfExists(packageName);
+                if (p == null) {
+                    return null;
+                }
+
+                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
                 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
                     return null;
                 }
@@ -1938,9 +2256,6 @@
          */
         @Override
         public void onSystemLocaleChangedNoLock() {
-            if (!FEATURE_ENABLED) {
-                return;
-            }
             // DO NOT HOLD ANY LOCKS HERE.
 
             // We want to reset throttling for all packages for all users.  But we can't just do so
@@ -1953,8 +2268,29 @@
             //
             // This allows ShortcutUser's to detect the system locale change, so they can reset
             // counters.
-            mLocaleChangeSequenceNumber.incrementAndGet();
-            postToHandler(() -> scheduleSaveBaseState());
+
+            // Ignore all callback during system boot.
+            if (mBootCompleted.get()) {
+                mLocaleChangeSequenceNumber.incrementAndGet();
+                if (DEBUG) {
+                    Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get());
+                }
+                injectPostToHandler(() -> handleLocaleChanged());
+            }
+        }
+    }
+
+    void handleLocaleChanged() {
+        if (DEBUG) {
+            Slog.d(TAG, "handleLocaleChanged");
+        }
+        scheduleSaveBaseState();
+
+        final long token = injectClearCallingIdentity();
+        try {
+            forEachLoadedUserLocked(u -> u.forAllPackages(p -> p.resolveResourceStrings()));
+        } finally {
+            injectRestoreCallingIdentity(token);
         }
     }
 
@@ -1995,32 +2331,45 @@
         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(
-                            this, 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);
-                }
+                final long now = injectCurrentTimeMillis();
+
+                // Then for each installed app, publish manifest shortcuts when needed.
+                forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
+                    user.handlePackageAddedOrUpdated(ai.packageName);
+                });
+
+                // Write the time just before the scan, because there may be apps that have just
+                // been updated, and we want to catch them in the next time.
+                user.setLastAppScanTime(now);
+                scheduleSaveUser(ownerUserId);
             }
+        } finally {
+            logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
         }
+        verifyStates();
     }
 
     private void handlePackageAdded(String packageName, @UserIdInt int userId) {
@@ -2028,9 +2377,11 @@
             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);
         }
+        verifyStates();
     }
 
     private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
@@ -2039,15 +2390,14 @@
                     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(this, packageName, versionCode);
         }
+        verifyStates();
     }
 
     private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
@@ -2056,6 +2406,8 @@
                     packageUserId));
         }
         cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
+
+        verifyStates();
     }
 
     private void handlePackageDataCleared(String packageName, int packageUserId) {
@@ -2064,14 +2416,22 @@
                     packageUserId));
         }
         cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
+
+        verifyStates();
     }
 
     // === 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 {
@@ -2086,10 +2446,11 @@
         }
     }
 
+    @Nullable
     @VisibleForTesting
     PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
             boolean getSignatures) {
-        final long start = System.currentTimeMillis();
+        final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
@@ -2108,9 +2469,10 @@
         }
     }
 
+    @Nullable
     @VisibleForTesting
     ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
-        final long start = System.currentTimeMillis();
+        final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
@@ -2125,6 +2487,68 @@
         }
     }
 
+    @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<PackageInfo> injectInstalledPackages(@UserIdInt int userId) {
+        final long start = injectElapsedRealtime();
+        final long token = injectClearCallingIdentity();
+        try {
+            final ParceledListSlice<PackageInfo> parceledList =
+                    mIPackageManager.getInstalledPackages(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_PACKAGES, start);
+        }
+    }
+
+    private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime,
+            Consumer<ApplicationInfo> callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime);
+        }
+        final List<PackageInfo> list = injectInstalledPackages(userId);
+        for (int i = list.size() - 1; i >= 0; i--) {
+            final PackageInfo pi = list.get(i);
+
+            if (((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
+                    && (pi.lastUpdateTime >= lastScanTime)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Found updated package " + pi.packageName);
+                }
+                callback.accept(pi.applicationInfo);
+            }
+        }
+    }
+
     private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
         final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
         return (ai != null) && ((ai.flags & flags) == flags);
@@ -2134,15 +2558,26 @@
         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;
+    @Nullable
+    XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+        return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
+    }
+
+    @Nullable
+    Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) {
+        final long start = injectElapsedRealtime();
+        final long token = injectClearCallingIdentity();
+        try {
+            return mContext.getPackageManager().getResourcesForApplicationAsUser(
+                    packageName, userId);
+        } catch (NameNotFoundException e) {
+            Slog.e(TAG, "Resources for package " + packageName + " not found");
+            return null;
+        } finally {
+            injectRestoreCallingIdentity(token);
+
+            logDurationStat(Stats.GET_APPLICATION_RESOURCES, start);
         }
-        return ai.versionCode;
     }
 
     // === Backup & restore ===
@@ -2168,7 +2603,7 @@
                 return null;
             }
 
-            user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
+            user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave());
 
             // Then save.
             final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
@@ -2283,11 +2718,17 @@
                 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_PACKAGES, "getInstalledPackages");
+                dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
+                dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources");
+                dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup");
             }
 
             for (int i = 0; i < mUsers.size(); i++) {
                 pw.println();
-                mUsers.valueAt(i).dump(this, pw, "  ");
+                mUsers.valueAt(i).dump(pw, "  ");
             }
 
             pw.println();
@@ -2499,8 +2940,7 @@
 
         private void clearLauncher() {
             synchronized (mLock) {
-                getUserShortcutsLocked(mUserId).setLauncherComponent(
-                        ShortcutService.this, null);
+                getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null);
             }
         }
 
@@ -2510,7 +2950,7 @@
                 hasShortcutHostPermissionInner("-", mUserId);
 
                 getOutPrintWriter().println("Launcher: "
-                        + getUserShortcutsLocked(mUserId).getLauncherComponent());
+                        + getUserShortcutsLocked(mUserId).getDefaultLauncherComponent());
             }
         }
 
@@ -2663,15 +3103,47 @@
     }
 
     @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);
         }
     }
+
+    /**
+     * Control whether {@link #verifyStates} should be performed.  We always perform it during unit
+     * tests.
+     */
+    @VisibleForTesting
+    boolean injectShouldPerformVerification() {
+        return DEBUG;
+    }
+
+    /**
+     * Check various internal states and throws if there's any inconsistency.
+     * This is normally only enabled during unit tests.
+     */
+    final void verifyStates() {
+        if (injectShouldPerformVerification()) {
+            verifyStatesInner();
+        }
+    }
+
+    private void verifyStatesInner() {
+        synchronized (this) {
+            forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 7d19a78..f8ee325 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -16,6 +16,7 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.text.format.Formatter;
@@ -23,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;
 
@@ -39,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;
@@ -48,6 +52,7 @@
 
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no";
+    private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time";
 
     static final class PackageWithUser {
         final int userId;
@@ -87,6 +92,8 @@
         }
     }
 
+    final ShortcutService mService;
+
     @UserIdInt
     private final int mUserId;
 
@@ -97,11 +104,14 @@
     private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
 
     /** Default launcher that can access the launcher apps APIs. */
-    private ComponentName mLauncherComponent;
+    private ComponentName mDefaultLauncherComponent;
 
     private long mKnownLocaleChangeSequenceNumber;
 
-    public ShortcutUser(int userId) {
+    private long mLastAppScanTime;
+
+    public ShortcutUser(ShortcutService service, int userId) {
+        mService = service;
         mUserId = userId;
     }
 
@@ -109,6 +119,14 @@
         return mUserId;
     }
 
+    public long getLastAppScanTime() {
+        return mLastAppScanTime;
+    }
+
+    public void setLastAppScanTime(long lastAppScanTime) {
+        mLastAppScanTime = lastAppScanTime;
+    }
+
     // We don't expose this directly to non-test code because only ShortcutUser should add to/
     // remove from it.
     @VisibleForTesting
@@ -116,10 +134,14 @@
         return mPackages;
     }
 
-    public ShortcutPackage removePackage(@NonNull ShortcutService s, @NonNull String packageName) {
+    public boolean hasPackage(@NonNull String packageName) {
+        return mPackages.containsKey(packageName);
+    }
+
+    public ShortcutPackage removePackage(@NonNull String packageName) {
         final ShortcutPackage removed = mPackages.remove(packageName);
 
-        s.cleanupBitmapsForPackage(mUserId, packageName);
+        mService.cleanupBitmapsForPackage(mUserId, packageName);
 
         return removed;
     }
@@ -136,23 +158,33 @@
                 launcher.getPackageName()), launcher);
     }
 
+    @Nullable
     public ShortcutLauncher removeLauncher(
             @UserIdInt int packageUserId, @NonNull String packageName) {
         return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
     }
 
-    public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
-        ShortcutPackage ret = mPackages.get(packageName);
-        if (ret == null) {
-            ret = new ShortcutPackage(s, this, mUserId, packageName);
-            mPackages.put(packageName, ret);
-        } else {
-            ret.attemptToRestoreIfNeededAndSave(s);
+    @Nullable
+    public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
+        final ShortcutPackage ret = mPackages.get(packageName);
+        if (ret != null) {
+            ret.attemptToRestoreIfNeededAndSave();
         }
         return ret;
     }
 
-    public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
+    @NonNull
+    public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
+        ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
+        if (ret == null) {
+            ret = new ShortcutPackage(this, mUserId, packageName);
+            mPackages.put(packageName, ret);
+        }
+        return ret;
+    }
+
+    @NonNull
+    public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
             @UserIdInt int launcherUserId) {
         final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
         ShortcutLauncher ret = mLaunchers.get(key);
@@ -160,7 +192,7 @@
             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
             mLaunchers.put(key, ret);
         } else {
-            ret.attemptToRestoreIfNeededAndSave(s);
+            ret.attemptToRestoreIfNeededAndSave();
         }
         return ret;
     }
@@ -197,8 +229,8 @@
     /**
      * Reset all throttling counters for all packages, if there has been a system locale change.
      */
-    public void resetThrottlingIfNeeded(ShortcutService s) {
-        final long currentNo = s.getLocaleChangeSequenceNumber();
+    public void resetThrottlingIfNeeded() {
+        final long currentNo = mService.getLocaleChangeSequenceNumber();
         if (mKnownLocaleChangeSequenceNumber < currentNo) {
             if (ShortcutService.DEBUG) {
                 Slog.d(TAG, "LocaleChange detected for user " + mUserId);
@@ -206,61 +238,64 @@
 
             mKnownLocaleChangeSequenceNumber = currentNo;
 
-            forAllPackages(p -> p.resetRateLimiting(s));
+            forAllPackages(p -> p.resetRateLimiting());
 
-            s.scheduleSaveUser(mUserId);
+            mService.scheduleSaveUser(mUserId);
         }
     }
 
-    /**
-     * Called when a package is updated.
-     */
-    public void handlePackageUpdated(ShortcutService s, @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);
+            }
         }
-        getPackageShortcuts(s, packageName).handlePackageUpdated(s, newVersionCode);
     }
 
     public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
             @UserIdInt int packageUserId) {
         forPackageItem(packageName, packageUserId, spi -> {
-            spi.attemptToRestoreIfNeededAndSave(s);
+            spi.attemptToRestoreIfNeededAndSave();
         });
     }
 
-    public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
+    public void saveToXml(XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException {
         out.startTag(null, TAG_ROOT);
 
         ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER,
                 mKnownLocaleChangeSequenceNumber);
+        ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
+                mLastAppScanTime);
 
         ShortcutService.writeTagValue(out, TAG_LAUNCHER,
-                mLauncherComponent);
+                mDefaultLauncherComponent);
 
         // Can't use forEachPackageItem due to the checked exceptions.
         {
             final int size = mLaunchers.size();
             for (int i = 0; i < size; i++) {
-                saveShortcutPackageItem(s, out, mLaunchers.valueAt(i), forBackup);
+                saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
             }
         }
         {
             final int size = mPackages.size();
             for (int i = 0; i < size; i++) {
-                saveShortcutPackageItem(s, out, mPackages.valueAt(i), forBackup);
+                saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
             }
         }
 
         out.endTag(null, TAG_ROOT);
     }
 
-    private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out,
+    private void saveShortcutPackageItem(XmlSerializer out,
             ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
         if (forBackup) {
-            if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
+            if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
                 return; // Don't save.
             }
             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
@@ -272,11 +307,18 @@
 
     public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
             boolean fromBackup) throws IOException, XmlPullParserException {
-        final ShortcutUser ret = new ShortcutUser(userId);
+        final ShortcutUser ret = new ShortcutUser(s, userId);
 
         ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
                 ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
 
+        // If lastAppScanTime is in the future, that means the clock went backwards.
+        // Just scan all apps again.
+        final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
+                ATTR_LAST_APP_SCAN_TIME);
+        final long currentTime = s.injectCurrentTimeMillis();
+        ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
+
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -290,7 +332,7 @@
             if (depth == outerDepth + 1) {
                 switch (tag) {
                     case TAG_LAUNCHER: {
-                        ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
+                        ret.mDefaultLauncherComponent = ShortcutService.parseComponentNameAttribute(
                                 parser, ATTR_VALUE);
                         continue;
                     }
@@ -315,16 +357,16 @@
         return ret;
     }
 
-    public ComponentName getLauncherComponent() {
-        return mLauncherComponent;
+    public ComponentName getDefaultLauncherComponent() {
+        return mDefaultLauncherComponent;
     }
 
-    public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
-        if (Objects.equal(mLauncherComponent, launcherComponent)) {
+    public void setDefaultLauncherComponent(ComponentName launcherComponent) {
+        if (Objects.equal(mDefaultLauncherComponent, launcherComponent)) {
             return;
         }
-        mLauncherComponent = launcherComponent;
-        s.scheduleSaveUser(mUserId);
+        mDefaultLauncherComponent = launcherComponent;
+        mService.scheduleSaveUser(mUserId);
     }
 
     public void resetThrottling() {
@@ -333,36 +375,38 @@
         }
     }
 
-    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         pw.print(prefix);
         pw.print("User: ");
         pw.print(mUserId);
         pw.print("  Known locale seq#: ");
         pw.print(mKnownLocaleChangeSequenceNumber);
+        pw.print("  Last app scan: ");
+        pw.print(mLastAppScanTime);
         pw.println();
 
         prefix += prefix + "  ";
 
         pw.print(prefix);
         pw.print("Default launcher: ");
-        pw.print(mLauncherComponent);
+        pw.print(mDefaultLauncherComponent);
         pw.println();
 
         for (int i = 0; i < mLaunchers.size(); i++) {
-            mLaunchers.valueAt(i).dump(s, pw, prefix);
+            mLaunchers.valueAt(i).dump(pw, prefix);
         }
 
         for (int i = 0; i < mPackages.size(); i++) {
-            mPackages.valueAt(i).dump(s, pw, prefix);
+            mPackages.valueAt(i).dump(pw, prefix);
         }
 
         pw.println();
         pw.print(prefix);
         pw.println("Bitmap directories: ");
-        dumpDirectorySize(s, pw, prefix + "  ", s.getUserBitmapFilePath(mUserId));
+        dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
     }
 
-    private void dumpDirectorySize(@NonNull ShortcutService s, @NonNull PrintWriter pw,
+    private void dumpDirectorySize(@NonNull PrintWriter pw,
             @NonNull String prefix, File path) {
         int numFiles = 0;
         long size = 0;
@@ -373,7 +417,7 @@
                     numFiles++;
                     size += child.length();
                 } else if (child.isDirectory()) {
-                    dumpDirectorySize(s, pw, prefix + "  ", child);
+                    dumpDirectorySize(pw, prefix + "  ", child);
                 }
             }
         }
@@ -385,7 +429,7 @@
         pw.print(" files, size=");
         pw.print(size);
         pw.print(" (");
-        pw.print(Formatter.formatFileSize(s.mContext, size));
+        pw.print(Formatter.formatFileSize(mService.mContext, size));
         pw.println(")");
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a85064b..0bf0cac 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -27,11 +27,13 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -70,6 +72,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.AtomicFile;
 import android.util.IntArray;
 import android.util.Log;
@@ -452,6 +455,8 @@
             // user restriction was not a default guest restriction.
             setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, currentGuestUser.id);
         }
+
+        maybeInitializeDemoMode(UserHandle.USER_SYSTEM);
         mContext.registerReceiver(mDisableQuietModeCallback,
                 new IntentFilter(ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK),
                 null, mHandler);
@@ -2160,6 +2165,7 @@
         final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
         final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
         final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
+        final boolean isDemo = (flags & UserInfo.FLAG_DEMO) != 0;
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo;
         UserData userData;
@@ -2177,8 +2183,8 @@
                     Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
                     return null;
                 }
-                if (!isGuest && !isManagedProfile && isUserLimitReached()) {
-                    // If we're not adding a guest user or a managed profile and the limit has
+                if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
+                    // If we're not adding a guest/demo user or a managed profile and the limit has
                     // been reached, cannot add a user.
                     return null;
                 }
@@ -2204,7 +2210,8 @@
                         return null;
                     }
                 }
-                if (!UserManager.isSplitSystemUser() && (flags & UserInfo.FLAG_EPHEMERAL) != 0) {
+                if (!UserManager.isSplitSystemUser() && (flags & UserInfo.FLAG_EPHEMERAL) != 0
+                        && (flags & UserInfo.FLAG_DEMO) == 0) {
                     Log.e(LOG_TAG,
                             "Ephemeral users are supported on split-system-user systems only.");
                     return null;
@@ -2855,6 +2862,8 @@
                 mPm.onBeforeUserStartUninitialized(userId);
             }
         }
+
+        maybeInitializeDemoMode(userId);
     }
 
     /**
@@ -2869,6 +2878,7 @@
 
     /**
      * Make a note of the last started time of a user and do some cleanup.
+     * This is called with ActivityManagerService lock held.
      * @param userId the user that was just foregrounded
      */
     public void onUserLoggedIn(@UserIdInt int userId) {
@@ -2886,6 +2896,24 @@
         scheduleWriteUser(userData);
     }
 
+    private void maybeInitializeDemoMode(int userId) {
+        if (UserManager.isDeviceInDemoMode(mContext)) {
+            String demoLauncher =
+                    mContext.getResources().getString(
+                            com.android.internal.R.string.config_demoModeLauncherComponent);
+            if (!TextUtils.isEmpty(demoLauncher)) {
+                ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher);
+                try {
+                    AppGlobals.getPackageManager().setComponentEnabledSetting(componentToEnable,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
+                            /* userId= */ userId);
+                } catch (RemoteException re) {
+                    // Internal, shouldn't happen
+                }
+            }
+        }
+    }
+
     /**
      * Returns the next available user id, filling in any holes in the ids.
      * TODO: May not be a good idea to recycle ids, in case it results in confusion
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 38a3f42..c082143 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -33,6 +33,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.service.persistentdata.PersistentDataBlockManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
@@ -424,6 +425,14 @@
                             android.provider.Settings.Global.SAFE_BOOT_DISALLOWED,
                             newValue ? 1 : 0);
                     break;
+                case UserManager.DISALLOW_FACTORY_RESET:
+                    if (newValue) {
+                        PersistentDataBlockManager manager = (PersistentDataBlockManager) context
+                                .getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+                        if (manager != null && manager.getOemUnlockEnabled()) {
+                            manager.setOemUnlockEnabled(false);
+                        }
+                    }
             }
         } finally {
             Binder.restoreCallingIdentity(id);
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 5ef518e..6fc15f0 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -1145,7 +1145,7 @@
         public GlobalActionsDialog(Context context, AlertParams params) {
             super(context, getDialogTheme(context));
             mContext = getContext();
-            mAlert = new AlertController(mContext, this, getWindow());
+            mAlert = AlertController.create(mContext, this, getWindow());
             mAdapter = (MyAdapter) params.mAdapter;
             mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
             params.apply(mAlert);
diff --git a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
index a77d512..2e32fe3 100644
--- a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
@@ -142,7 +143,8 @@
             if (!disabled
                     && (DEBUG_SHOW_EVERY_TIME || !mConfirmed)
                     && userSetupComplete
-                    && !mVrModeEnabled) {
+                    && !mVrModeEnabled
+                    && !UserManager.isDeviceInDemoMode(mContext)) {
                 mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
             }
         } else {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a4408fc..f9f0fd9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -262,6 +262,10 @@
     private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
             "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
 
+    private static final int NAV_BAR_BOTTOM = 0;
+    private static final int NAV_BAR_RIGHT = 1;
+    private static final int NAV_BAR_LEFT = 2;
+
     /**
      * Keyguard stuff
      */
@@ -354,9 +358,8 @@
     int mStatusBarHeight;
     WindowState mNavigationBar = null;
     boolean mHasNavigationBar = false;
-    boolean mCanHideNavigationBar = false;
     boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side?
-    boolean mNavigationBarOnBottom = true; // is the navigation bar on the bottom *right now*?
+    int mNavigationBarPosition = NAV_BAR_BOTTOM;
     int[] mNavigationBarHeightForRotationDefault = new int[4];
     int[] mNavigationBarWidthForRotationDefault = new int[4];
     int[] mNavigationBarHeightForRotationInCarMode = new int[4];
@@ -1683,13 +1686,19 @@
                     }
                     @Override
                     public void onSwipeFromBottom() {
-                        if (mNavigationBar != null && mNavigationBarOnBottom) {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_BOTTOM) {
                             requestTransientBars(mNavigationBar);
                         }
                     }
                     @Override
                     public void onSwipeFromRight() {
-                        if (mNavigationBar != null && !mNavigationBarOnBottom) {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_RIGHT) {
+                            requestTransientBars(mNavigationBar);
+                        }
+                    }
+                    @Override
+                    public void onSwipeFromLeft() {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_LEFT) {
                             requestTransientBars(mNavigationBar);
                         }
                     }
@@ -2782,8 +2791,8 @@
             if (win.getAttrs().windowAnimations != 0) {
                 return 0;
             }
-            // This can be on either the bottom or the right.
-            if (mNavigationBarOnBottom) {
+            // This can be on either the bottom or the right or the left.
+            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
                 if (transit == TRANSIT_EXIT
                         || transit == TRANSIT_HIDE) {
                     return R.anim.dock_bottom_exit;
@@ -2791,7 +2800,7 @@
                         || transit == TRANSIT_SHOW) {
                     return R.anim.dock_bottom_enter;
                 }
-            } else {
+            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
                 if (transit == TRANSIT_EXIT
                         || transit == TRANSIT_HIDE) {
                     return R.anim.dock_right_exit;
@@ -2799,6 +2808,14 @@
                         || transit == TRANSIT_SHOW) {
                     return R.anim.dock_right_enter;
                 }
+            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                if (transit == TRANSIT_EXIT
+                        || transit == TRANSIT_HIDE) {
+                    return R.anim.dock_left_exit;
+                } else if (transit == TRANSIT_ENTER
+                        || transit == TRANSIT_SHOW) {
+                    return R.anim.dock_left_enter;
+                }
             }
         } else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
             return selectDockedDividerAnimationLw(win, transit);
@@ -2827,10 +2844,12 @@
         // If the divider is behind the navigation bar, don't animate.
         final Rect frame = win.getFrameLw();
         final boolean behindNavBar = mNavigationBar != null
-                && ((mNavigationBarOnBottom
+                && ((mNavigationBarPosition == NAV_BAR_BOTTOM
                         && frame.top + insets >= mNavigationBar.getFrameLw().top)
-                || (!mNavigationBarOnBottom
-                        && frame.left + insets >= mNavigationBar.getFrameLw().left));
+                || (mNavigationBarPosition == NAV_BAR_RIGHT
+                        && frame.left + insets >= mNavigationBar.getFrameLw().left)
+                || (mNavigationBarPosition == NAV_BAR_LEFT
+                        && frame.right - insets <= mNavigationBar.getFrameLw().right));
         final boolean landscape = frame.height() > frame.width();
         final boolean offscreenLandscape = landscape && (frame.right - insets <= 0
                 || frame.left + insets >= win.getDisplayFrameLw().right);
@@ -4023,7 +4042,7 @@
             navVisible |= !canHideNavigationBar();
 
             boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,
-                    displayRotation, uiMode, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
+                    displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
                     navAllowedHidden, statusBarExpandedNotKeyguard);
             if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
                     mDockLeft, mDockTop, mDockRight, mDockBottom));
@@ -4102,8 +4121,8 @@
     }
 
     private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation,
-            int uiMode, int overscanRight, int overscanBottom, Rect dcf, boolean navVisible,
-            boolean navTranslucent, boolean navAllowedHidden,
+            int uiMode, int overscanLeft, int overscanRight, int overscanBottom, Rect dcf,
+            boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
             boolean statusBarExpandedNotKeyguard) {
         if (mNavigationBar != null) {
             boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
@@ -4111,8 +4130,9 @@
             // size.  We need to do this directly, instead of relying on
             // it to bubble up from the nav bar, because this needs to
             // change atomically with screen rotations.
-            mNavigationBarOnBottom = isNavigationBarOnBottom(displayWidth, displayHeight);
-            if (mNavigationBarOnBottom) {
+            mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight,
+                    displayRotation);
+            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
                 // It's a system nav bar or a portrait screen; nav bar goes on bottom.
                 int top = displayHeight - overscanBottom
                         - getNavigationBarHeight(displayRotation, uiMode);
@@ -4138,7 +4158,7 @@
                     // we can tell the app that it is covered by it.
                     mSystemBottom = mTmpNavigationFrame.top;
                 }
-            } else {
+            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
                 // Landscape screen; nav bar goes to the right.
                 int left = displayWidth - overscanRight
                         - getNavigationBarWidth(displayRotation, uiMode);
@@ -4164,6 +4184,33 @@
                     // we can tell the app that it is covered by it.
                     mSystemRight = mTmpNavigationFrame.left;
                 }
+            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                // Seascape screen; nav bar goes to the left.
+                int right = overscanLeft + getNavigationBarWidth(displayRotation, uiMode);
+                mTmpNavigationFrame.set(overscanLeft, 0, right, displayHeight);
+                mStableLeft = mStableFullscreenLeft = mTmpNavigationFrame.right;
+                if (transientNavBarShowing) {
+                    mNavigationBarController.setBarShowingLw(true);
+                } else if (navVisible) {
+                    mNavigationBarController.setBarShowingLw(true);
+                    mDockLeft = mTmpNavigationFrame.right;
+                    // TODO: not so sure about those:
+                    mRestrictedScreenLeft = mRestrictedOverscanScreenLeft = mDockLeft;
+                    mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
+                    mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
+                } else {
+                    // We currently want to hide the navigation UI - unless we expanded the status
+                    // bar.
+                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+                }
+                if (navVisible && !navTranslucent && !navAllowedHidden
+                        && !mNavigationBar.isAnimatingLw()
+                        && !mNavigationBarController.wasRecentlyTranslucent()) {
+                    // If the nav bar is currently requested to be visible,
+                    // and not in the process of animating on or off, then
+                    // we can tell the app that it is covered by it.
+                    mSystemLeft = mTmpNavigationFrame.right;
+                }
             }
             // Make sure the content and current rectangles are updated to
             // account for the restrictions from the navigation bar.
@@ -4184,8 +4231,15 @@
         return false;
     }
 
-    private boolean isNavigationBarOnBottom(int displayWidth, int displayHeight) {
-        return !mNavigationBarCanMove || displayWidth < displayHeight;
+    private int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
+        if (mNavigationBarCanMove && displayWidth > displayHeight) {
+            if (displayRotation == Surface.ROTATION_270) {
+                return NAV_BAR_LEFT;
+            } else {
+                return NAV_BAR_RIGHT;
+            }
+        }
+        return NAV_BAR_BOTTOM;
     }
 
     /** {@inheritDoc} */
@@ -4361,7 +4415,11 @@
             if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
                 // The status bar forces the navigation bar while it's visible. Make sure the IME
                 // avoids the navigation bar in that case.
-                pf.right = df.right = of.right = cf.right = vf.right = mStableRight;
+                if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+                    pf.right = df.right = of.right = cf.right = vf.right = mStableRight;
+                } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                    pf.left = df.left = of.left = cf.left = vf.left = mStableLeft;
+                }
             }
             // IM dock windows always go to the bottom of the screen.
             attrs.gravity = Gravity.BOTTOM;
@@ -4390,6 +4448,8 @@
             } else {
                 vf.set(cf);
             }
+        } else if (attrs.type == TYPE_WALLPAPER) {
+           layoutWallpaper(win, pf, df, of, cf);
         } else if (win == mStatusBar) {
             pf.left = df.left = of.left = mUnrestrictedScreenLeft;
             pf.top = df.top = of.top = mUnrestrictedScreenTop;
@@ -4613,17 +4673,6 @@
                             + mOverscanScreenWidth;
                     pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
                             + mOverscanScreenHeight;
-                } else if (attrs.type == TYPE_WALLPAPER) {
-                    // The wallpaper also has Real Ultimate Power, but we want to tell
-                    // it about the overscan area.
-                    pf.left = df.left = mOverscanScreenLeft;
-                    pf.top = df.top = mOverscanScreenTop;
-                    pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
-                    pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
-                    of.left = cf.left = mUnrestrictedScreenLeft;
-                    of.top = cf.top = mUnrestrictedScreenTop;
-                    of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                    of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
                 } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
                         && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
                         && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
@@ -4816,6 +4865,20 @@
         }
     }
 
+    private void layoutWallpaper(WindowState win, Rect pf, Rect df, Rect of, Rect cf) {
+
+        // The wallpaper also has Real Ultimate Power, but we want to tell
+        // it about the overscan area.
+        pf.left = df.left = mOverscanScreenLeft;
+        pf.top = df.top = mOverscanScreenTop;
+        pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
+        pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
+        of.left = cf.left = mUnrestrictedScreenLeft;
+        of.top = cf.top = mUnrestrictedScreenTop;
+        of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+        of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+    }
+
     private void offsetInputMethodWindowLw(WindowState win) {
         int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
         top += win.getGivenContentInsetsLw().top;
@@ -4997,7 +5060,7 @@
 
         // Keep track of the window if it's dimming but not necessarily fullscreen.
         final boolean reallyVisible = win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw();
-        if (mTopFullscreenOpaqueOrDimmingWindowState == null &&  reallyVisible
+        if (mTopFullscreenOpaqueOrDimmingWindowState == null && reallyVisible
                 && win.isDimming() && StackId.normallyFullscreenWindows(stackId)) {
             mTopFullscreenOpaqueOrDimmingWindowState = win;
         }
@@ -5655,6 +5718,17 @@
                 break;
             }
 
+            case KeyEvent.KEYCODE_FP_NAV_DOWN:
+                // fall through
+            case KeyEvent.KEYCODE_FP_NAV_UP:
+                // fall through
+            case KeyEvent.KEYCODE_FP_NAV_LEFT:
+                // fall through
+            case KeyEvent.KEYCODE_FP_NAV_RIGHT: {
+                interceptStatusBarKey(event);
+                break;
+            }
+
             case KeyEvent.KEYCODE_SLEEP: {
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
@@ -5776,6 +5850,32 @@
     }
 
     /**
+     * Handle statusbar expansion events.
+     * @param event
+     */
+    private void interceptStatusBarKey(KeyEvent event) {
+        final int e = event.getKeyCode();
+        if (event.getAction() == KeyEvent.ACTION_UP && areSystemNavigationKeysEnabled()) {
+            boolean doOpen = false;
+            boolean doClose = false;
+            doOpen = (e == KeyEvent.KEYCODE_FP_NAV_DOWN);
+            doClose = (e == KeyEvent.KEYCODE_FP_NAV_UP);
+            IStatusBarService sbar = getStatusBarService();
+            if (sbar != null) {
+                try {
+                    if (doOpen) {
+                        sbar.expandNotificationsPanel();
+                    } else if (doClose) {
+                        sbar.collapsePanels();
+                    }
+                } catch (RemoteException e1) {
+                    // oops, no statusbar. Ignore event.
+                }
+            }
+        }
+    }
+
+    /**
      * Returns true if the key can have global actions attached to it.
      * We reserve all power management keys for the system since they require
      * very careful handling.
@@ -6425,10 +6525,13 @@
 
         // Only navigation bar
         if (mNavigationBar != null) {
-            if (isNavigationBarOnBottom(displayWidth, displayHeight)) {
+            int position = navigationBarPosition(displayWidth, displayHeight, displayRotation);
+            if (position == NAV_BAR_BOTTOM) {
                 outInsets.bottom = getNavigationBarHeight(displayRotation, mUiMode);
-            } else {
+            } else if (position == NAV_BAR_RIGHT) {
                 outInsets.right = getNavigationBarWidth(displayRotation, mUiMode);
+            } else if (position == NAV_BAR_LEFT) {
+                outInsets.left = getNavigationBarWidth(displayRotation, mUiMode);
             }
         }
     }
@@ -6774,9 +6877,7 @@
             @Override public void run() {
                 if (mBootMsgDialog == null) {
                     int theme;
-                    if (mHasFeatureWatch) {
-                        theme = com.android.internal.R.style.Theme_Micro_Dialog_Alert;
-                    } else if (mContext.getPackageManager().hasSystemFeature(FEATURE_TELEVISION)) {
+                    if (mContext.getPackageManager().hasSystemFeature(FEATURE_TELEVISION)) {
                         theme = com.android.internal.R.style.Theme_Leanback_Dialog_Alert;
                     } else {
                         theme = 0;
@@ -7165,6 +7266,11 @@
                 Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
     }
 
+    private boolean areSystemNavigationKeysEnabled() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SYSTEM_NAVIGATION_KEYS_ENABLED, 0) == 1;
+    }
+
     @Override
     public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) {
         if (!mVibrator.hasVibrator()) {
diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
index 80e4341..598c58e 100644
--- a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -43,6 +43,7 @@
     private static final int SWIPE_FROM_TOP = 1;
     private static final int SWIPE_FROM_BOTTOM = 2;
     private static final int SWIPE_FROM_RIGHT = 3;
+    private static final int SWIPE_FROM_LEFT = 4;
 
     private final Context mContext;
     private final int mSwipeStartThreshold;
@@ -127,6 +128,9 @@
                     } else if (swipe == SWIPE_FROM_RIGHT) {
                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
                         mCallbacks.onSwipeFromRight();
+                    } else if (swipe == SWIPE_FROM_LEFT) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft");
+                        mCallbacks.onSwipeFromLeft();
                     }
                 }
                 break;
@@ -229,6 +233,11 @@
                 && elapsed < SWIPE_TIMEOUT_MS) {
             return SWIPE_FROM_RIGHT;
         }
+        if (fromX <= mSwipeStartThreshold
+                && x > fromX + mSwipeDistanceThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_LEFT;
+        }
         return SWIPE_NONE;
     }
 
@@ -265,6 +274,7 @@
         void onSwipeFromTop();
         void onSwipeFromBottom();
         void onSwipeFromRight();
+        void onSwipeFromLeft();
         void onFling(int durationMs);
         void onDown();
         void onUpOrCancel();
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index a32c017..8ef0acb 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -49,6 +49,7 @@
             "debug.orientation.log", false);
 
     private static final boolean USE_GRAVITY_SENSOR = false;
+    private static final int DEFAULT_BATCH_LATENCY = 100000;
 
     private Handler mHandler;
     private SensorManager mSensorManager;
@@ -118,7 +119,12 @@
                     Slog.d(TAG, "WindowOrientationListener enabled");
                 }
                 mOrientationJudge.resetLocked();
-                mSensorManager.registerListener(mOrientationJudge, mSensor, mRate, mHandler);
+                if (mSensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+                    mSensorManager.registerListener(
+                            mOrientationJudge, mSensor, mRate, DEFAULT_BATCH_LATENCY, mHandler);
+                } else {
+                    mSensorManager.registerListener(mOrientationJudge, mSensor, mRate, mHandler);
+                }
                 mEnabled = true;
             }
         }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 7108f4a..3ed6ec9 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -23,6 +23,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.am.RetailDemoModeServiceInternal;
 
 import android.app.ActivityManagerNative;
 import android.content.BroadcastReceiver;
@@ -91,6 +92,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final InputManagerInternal mInputManagerInternal;
     private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final RetailDemoModeServiceInternal mRetailDemoModeServiceInternal;
 
     private final NotifierHandler mHandler;
     private final Intent mScreenOnIntent;
@@ -136,6 +138,7 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+        mRetailDemoModeServiceInternal = LocalServices.getService(RetailDemoModeServiceInternal.class);
 
         mHandler = new NotifierHandler(looper);
         mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
@@ -534,7 +537,9 @@
             }
             mUserActivityPending = false;
         }
-
+        if (mRetailDemoModeServiceInternal != null) {
+            mRetailDemoModeServiceInternal.onUserActivity();
+        }
         mPolicy.userActivity();
     }
 
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 5b9d139..8ce2fd9 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -543,7 +543,8 @@
                 }
 
                 try {
-                    bluetoothOff = bluetooth == null || !bluetooth.isEnabled();
+                    bluetoothOff = bluetooth == null ||
+                            bluetooth.getState() == BluetoothAdapter.STATE_OFF;
                     if (!bluetoothOff) {
                         Log.w(TAG, "Disabling Bluetooth...");
                         bluetooth.disable(false);  // disable but don't persist new state
@@ -577,7 +578,7 @@
 
                     if (!bluetoothOff) {
                         try {
-                            bluetoothOff = !bluetooth.isEnabled();
+                            bluetoothOff = bluetooth.getState() == BluetoothAdapter.STATE_OFF;
                         } catch (RemoteException ex) {
                             Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
                             bluetoothOff = true;
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 2b9879e..7f97c46 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -1,5 +1,6 @@
 package com.android.server.wm;
 
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -250,6 +251,13 @@
                     duration = getDimLayerFadeDuration(duration);
                 }
                 state.dimLayer.show(dimLayer, dimAmount, duration);
+
+                // If we showed a dim layer, make sure to redo the layout because some things depend
+                // on whether a dim layer is showing or not.
+                if (targetAlpha == 0) {
+                    mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
+                    mDisplayContent.layoutNeeded = true;
+                }
             }
         } else if (state.dimLayer.getLayer() != dimLayer) {
             state.dimLayer.setLayer(dimLayer);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f59b2ff..8f8ba1d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -42,7 +42,9 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.IMountService;
+import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Slog;
@@ -55,6 +57,7 @@
 import com.android.internal.widget.ILockSettings;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.am.RetailDemoModeService;
 import com.android.server.audio.AudioService;
 import com.android.server.camera.CameraService;
 import com.android.server.clipboard.ClipboardService;
@@ -154,6 +157,8 @@
             "com.google.android.clockwork.ThermalObserver";
     private static final String WEAR_BLUETOOTH_SERVICE_CLASS =
             "com.google.android.clockwork.bluetooth.WearBluetoothService";
+    private static final String WEAR_TIME_SERVICE_CLASS =
+            "com.google.android.clockwork.time.WearTimeService";
     private static final String ACCOUNT_SERVICE_CLASS =
             "com.android.server.accounts.AccountManagerService$Lifecycle";
     private static final String CONTENT_SERVICE_CLASS =
@@ -176,7 +181,7 @@
      * visual content.
      */
     private static final int DEFAULT_SYSTEM_THEME =
-            com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar;
+            com.android.internal.R.style.Theme_DeviceDefault_System;
 
     private final int mFactoryTestMode;
     private Timer mProfilerSnapshotTimer;
@@ -1160,6 +1165,9 @@
 
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
             mSystemServiceManager.startService(WEAR_BLUETOOTH_SERVICE_CLASS);
+          if (!disableNonCoreServices) {
+              mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
+          }
         }
 
         // Before things start rolling, be sure we have decided whether
@@ -1177,6 +1185,11 @@
         // MMS service broker
         mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
 
+        if (Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 0) == 0 ||
+                UserManager.isDeviceInDemoMode(mSystemContext)) {
+            mSystemServiceManager.startService(RetailDemoModeService.class);
+        }
+
         // It is now time to start up the app processes...
 
         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "MakeVibratorServiceReady");
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 0437e1d..50e0662 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -12,6 +12,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    frameworks-base-testutils \
     services.core \
     services.devicepolicy \
     services.net \
@@ -19,7 +20,8 @@
     easymocklib \
     guava \
     android-support-test \
-    mockito-target
+    mockito-target \
+    ShortcutManagerTestUtils
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
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..f6d54fc
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_1.xml
@@ -0,0 +1,13 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+        android:shortcutIntentAction="action1"
+        android:shortcutIntentData="data1"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1_alt.xml b/services/tests/servicestests/res/xml/shortcut_1_alt.xml
new file mode 100644
index 0000000..bf14f49
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_1_alt.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1-alt"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+        android:shortcutIntentAction="action1"
+        android:shortcutIntentData="data1"
+    />
+</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..81a84b4
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="false"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@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..96ed382
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_2.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+        android:shortcutIntentAction="action1"
+        android:shortcutIntentData="http://a.b.c/"
+    />
+    <shortcut
+        android:shortcutId="ms2"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        android:shortcutCategories="android.shortcut.conversation"
+        android:shortcutIntentAction="action2"
+        android:shortcutIntentData="http://a.b.c/2"
+    />
+</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..2f814b7
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="action1"
+    />
+    <shortcut
+        android:shortcutId="ms1"
+        android:shortcutShortLabel="@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..56dba0e
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+        android:shortcutIntentAction="action1"
+        android:shortcutIntentData="http://a.b.c/1"
+    />
+    <shortcut
+        android:shortcutId="ms2"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        android:shortcutCategories="android.shortcut.conversation"
+        android:shortcutIntentAction="action2"
+    />
+    <shortcut
+        android:shortcutId="ms3"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms4"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms5"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_alt.xml b/services/tests/servicestests/res/xml/shortcut_5_alt.xml
new file mode 100644
index 0000000..74085d9
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5_alt.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1_alt"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+        android:shortcutIntentAction="action1"
+        android:shortcutIntentData="http://a.b.c/1"
+    />
+    <shortcut
+        android:shortcutId="ms2_alt"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        android:shortcutCategories="android.shortcut.conversation"
+        android:shortcutIntentAction="action2"
+    />
+    <shortcut
+        android:shortcutId="ms3_alt"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms4_alt"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms5_alt"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_reverse.xml b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
new file mode 100644
index 0000000..3d6eb22
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms5"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+        android:shortcutIntentAction="action1"
+        android:shortcutIntentData="http://a.b.c/1"
+    />
+    <shortcut
+        android:shortcutId="ms4"
+        android:enabled="true"
+        android:shortcutIcon="@drawable/icon2"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        android:shortcutCategories="android.shortcut.conversation"
+        android:shortcutIntentAction="action2"
+    />
+    <shortcut
+        android:shortcutId="ms3"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms2"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="ms1"
+        android:shortcutShortLabel="@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..5822496
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_1.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="x1"
+        android:shortcutShortLabel="@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..ca67ec7
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_2.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="manifest-shortcut-3"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+    <shortcut
+        android:shortcutId="x2"
+        android:shortcutShortLabel="@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..fb7b31c
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_3.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="manifest-shortcut-3"
+        android:shortcutShortLabel="@string/shortcut_title1"
+    />
+    <shortcut
+        android:shortcutId="x3"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutIntentAction="android.intent.action.VIEW"
+    />
+</shortcuts>
diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java
new file mode 100644
index 0000000..808f4dd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java
@@ -0,0 +1,130 @@
+/*
+ * 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.internal.util;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.test.mock.MockContentProvider;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * Fake for system settings.
+ *
+ * To use, ensure that the Context used by the test code returns a ContentResolver that uses this
+ * provider for the Settings authority:
+ *
+ *   class MyTestContext extends MockContext {
+ *       ...
+ *       private final MockContentResolver mContentResolver;
+ *       public MyTestContext(...) {
+ *           ...
+ *           mContentResolver = new MockContentResolver();
+ *           mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ *       }
+ *       ...
+ *       @Override
+ *       public ContentResolver getContentResolver() {
+ *           return mContentResolver;
+ *       }
+ *
+ * As long as the code under test is using the test Context, the actual code under test does not
+ * need to be modified, and can access Settings using the normal static methods:
+ *
+ *   Settings.Global.getInt(cr, "my_setting", 0);  // Returns 0.
+ *   Settings.Global.putInt(cr, "my_setting", 5);
+ *   Settings.Global.getInt(cr, "my_setting", 0);  // Returns 5.
+ *
+ * Note that this class cannot be used in the same process as real settings. This is because it
+ * works by passing an alternate ContentResolver to Settings operations. Unfortunately, the Settings
+ * class only fetches the content provider from the passed-in ContentResolver the first time it's
+ * used, and after that stores it in a per-process static.
+ *
+ * TODO: evaluate implementing settings change notifications. This would require:
+ *
+ * 1. Making ContentResolver#registerContentObserver non-final and overriding it in
+ *    MockContentResolver.
+ * 2. Making FakeSettingsProvider take a ContentResolver argument.
+ * 3. Calling ContentResolver#notifyChange(getUriFor(table, arg), ...) on every settings change.
+ */
+public class FakeSettingsProvider extends MockContentProvider {
+
+    private static final String TAG = FakeSettingsProvider.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final String[] TABLES = { "system", "secure", "global" };
+
+    private final HashMap<String, HashMap<String, String>> mTables = new HashMap<>();
+
+    public FakeSettingsProvider() {
+        for (int i = 0; i < TABLES.length; i++) {
+            mTables.put(TABLES[i], new HashMap<String, String>());
+        }
+    }
+
+    private Uri getUriFor(String table, String key) {
+        switch (table) {
+            case "system":
+                return Settings.System.getUriFor(key);
+            case "secure":
+                return Settings.Secure.getUriFor(key);
+            case "global":
+                return Settings.Global.getUriFor(key);
+            default:
+                throw new UnsupportedOperationException("Unknown settings table " + table);
+        }
+    }
+
+    public Bundle call(String method, String arg, Bundle extras) {
+        // Methods are "GET_system", "GET_global", "PUT_secure", etc.
+        String[] commands = method.split("_", 2);
+        String op = commands[0];
+        String table = commands[1];
+
+        Bundle out = new Bundle();
+        String value;
+        switch (op) {
+            case "GET":
+                value = mTables.get(table).get(arg);
+                if (value != null) {
+                    if (DBG) {
+                        Log.d(TAG, String.format("Returning fake setting %s.%s = %s",
+                                table, arg, value));
+                    }
+                    out.putString(Settings.NameValueTable.VALUE, value);
+                }
+                break;
+            case "PUT":
+                value = extras.getString(Settings.NameValueTable.VALUE, null);
+                if (DBG) {
+                    Log.d(TAG, String.format("Inserting fake setting %s.%s = %s",
+                            table, arg, value));
+                }
+                if (value != null) {
+                    mTables.get(table).put(arg, value);
+                } else {
+                    mTables.get(table).remove(arg);
+                }
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown command " + method);
+        }
+
+        return out;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java
new file mode 100644
index 0000000..a18737c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.internal.util;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for FakeSettingsProvider.
+ */
+public class FakeSettingsProviderTest extends AndroidTestCase {
+
+    private MockContentResolver mCr;
+
+    @Override
+    public void setUp() throws Exception {
+        mCr = new MockContentResolver();
+        mCr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+    }
+
+    @SmallTest
+    public void testBasicOperation() throws Exception {
+        String settingName = Settings.System.SCREEN_BRIGHTNESS;
+
+        try {
+            Settings.System.getInt(mCr, settingName);
+            fail("FakeSettingsProvider should start off empty.");
+        } catch (Settings.SettingNotFoundException expected) {}
+
+        // Check that fake settings can be written and read back.
+        Settings.System.putInt(mCr, settingName, 123);
+        assertEquals(123, Settings.System.getInt(mCr, settingName));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 4fae4a7..68a35f1 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -26,6 +26,7 @@
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
@@ -61,12 +62,15 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.Process;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.LogPrinter;
 
+import com.android.internal.util.FakeSettingsProvider;
 import com.android.internal.util.WakeupMessage;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
@@ -118,27 +122,24 @@
     }
 
     private class MockContext extends BroadcastInterceptingContext {
+        private final MockContentResolver mContentResolver;
+
         MockContext(Context base) {
             super(base);
+            mContentResolver = new MockContentResolver();
+            mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         }
 
         @Override
-        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            // PendingIntents sent by the AlarmManager are not intercepted by
-            // BroadcastInterceptingContext so we must really register the receiver.
-            // This shouldn't effect the real NetworkMonitors as the action contains a random token.
-            if (filter.getAction(0).startsWith("android.net.netmon.lingerExpired")) {
-                return getBaseContext().registerReceiver(receiver, filter);
-            } else {
-                return super.registerReceiver(receiver, filter);
-            }
-        }
-
-        @Override
-        public Object getSystemService (String name) {
+        public Object getSystemService(String name) {
             if (name == Context.CONNECTIVITY_SERVICE) return mCm;
             return super.getSystemService(name);
         }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mContentResolver;
+        }
     }
 
     /**
@@ -641,7 +642,6 @@
         public void waitForIdle() {
             waitForIdle(TIMEOUT_MS);
         }
-
     }
 
     private interface Criteria {
@@ -1477,6 +1477,73 @@
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
     }
 
+    @SmallTest
+    public void testMobileDataAlwaysOn() throws Exception {
+        final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+        final NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR).build();
+        mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+
+        final HandlerThread handlerThread = new HandlerThread("MobileDataAlwaysOnFactory");
+        handlerThread.start();
+        NetworkCapabilities filter = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET);
+        final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "testFactory", filter);
+        testFactory.setScoreFilter(40);
+
+        // Register the factory and expect it to start looking for a network.
+        testFactory.expectAddRequests(1);
+        testFactory.register();
+        testFactory.waitForNetworkRequests(1);
+        assertTrue(testFactory.getMyStartRequested());
+
+        // Bring up wifi. The factory stops looking for a network.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        testFactory.expectAddRequests(2);  // Because the default request changes score twice.
+        mWiFiNetworkAgent.connect(true);
+        testFactory.waitForNetworkRequests(1);
+        assertFalse(testFactory.getMyStartRequested());
+
+        ContentResolver cr = mServiceContext.getContentResolver();
+
+        // Turn on mobile data always on. The factory starts looking again.
+        testFactory.expectAddRequests(1);
+        Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 1);
+        mService.updateMobileDataAlwaysOn();
+        testFactory.waitForNetworkRequests(2);
+        assertTrue(testFactory.getMyStartRequested());
+
+        // Bring up cell data and check that the factory stops looking.
+        assertEquals(1, mCm.getAllNetworks().length);
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        testFactory.expectAddRequests(2);  // Because the cell request changes score twice.
+        mCellNetworkAgent.connect(true);
+        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE);
+        testFactory.waitForNetworkRequests(2);
+        assertFalse(testFactory.getMyStartRequested());  // Because the cell network outscores us.
+
+        // Check that cell data stays up.
+        mService.waitForIdle();
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        assertEquals(2, mCm.getAllNetworks().length);
+
+        // Turn off mobile data always on and expect the request to disappear...
+        testFactory.expectRemoveRequests(1);
+        Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 0);
+        mService.updateMobileDataAlwaysOn();
+        testFactory.waitForNetworkRequests(1);
+
+        // ...  and cell data to be torn down.
+        cellNetworkCallback.expectCallback(CallbackState.LOST);
+        assertEquals(1, mCm.getAllNetworks().length);
+
+        testFactory.unregister();
+        mCm.unregisterNetworkCallback(cellNetworkCallback);
+        handlerThread.quit();
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
new file mode 100644
index 0000000..a30b362
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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.connectivity.tethering;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetherInterfaceStateMachineTest {
+    private static final String IFACE_NAME = "testnet1";
+    private static final String UPSTREAM_IFACE = "upstream0";
+    private static final String UPSTREAM_IFACE2 = "upstream1";
+
+    @Mock private INetworkManagementService mNMService;
+    @Mock private INetworkStatsService mStatsService;
+    @Mock private IControlsTethering mTetherHelper;
+    @Mock private InterfaceConfiguration mInterfaceConfiguration;
+
+    private final TestLooper mLooper = new TestLooper();
+    private TetherInterfaceStateMachine mTestedSm;
+
+    private void initStateMachine(int interfaceType) throws Exception {
+        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
+                mNMService, mStatsService, mTetherHelper);
+        mTestedSm.start();
+        // Starting the state machine always puts us in a consistent state and notifies
+        // the test of the world that we've changed from an unknown to available state.
+        mLooper.dispatchAll();
+        reset(mNMService, mStatsService, mTetherHelper);
+        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+    }
+
+    private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
+        initStateMachine(interfaceType);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        if (upstreamIface != null) {
+            dispatchTetherConnectionChanged(upstreamIface);
+        }
+        reset(mNMService, mStatsService, mTetherHelper);
+        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+    }
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void startsOutAvailable() {
+        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
+                ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
+        mTestedSm.start();
+        mLooper.dispatchAll();
+        verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
+    }
+
+    @Test
+    public void shouldDoNothingUntilRequested() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+        final int [] NOOP_COMMANDS = {
+            TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
+            TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
+            TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR,
+            TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR,
+            TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR,
+            TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR,
+            TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED
+        };
+        for (int command : NOOP_COMMANDS) {
+            // None of these commands should trigger us to request action from
+            // the rest of the system.
+            dispatchCommand(command);
+            verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+        }
+    }
+
+    @Test
+    public void handlesImmediateInterfaceDown() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+        verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canBeTethered() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        InOrder inOrder = inOrder(mTetherHelper, mNMService);
+        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canUnrequestTethering() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
+        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canBeTetheredAsUsb() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_USB);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        InOrder inOrder = inOrder(mTetherHelper, mNMService);
+        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
+        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void handlesFirstUpstreamChange() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+
+        // Telling the state machine about its upstream interface triggers a little more configuration.
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+        InOrder inOrder = inOrder(mNMService);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void handlesChangingUpstream() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNMService, mStatsService);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canUnrequestTetheringWithUpstream() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void interfaceDownLeadsToUnavailable() throws Exception {
+        for (boolean shouldThrow : new boolean[]{true, false}) {
+            initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+
+            if (shouldThrow) {
+                doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
+            }
+            dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+            InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+            usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+            usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+                    IFACE_NAME, mInterfaceConfiguration);
+            usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                    IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+        }
+    }
+
+    @Test
+    public void usbShouldBeTornDownOnTetherError() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_USB);
+
+        doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+        usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+                IFACE_NAME, mInterfaceConfiguration);
+        usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
+    }
+
+    @Test
+    public void shouldTearDownUsbOnUpstreamError() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+
+        doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+        usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
+    }
+
+    /**
+     * Send a command to the state machine under test, and run the event loop to idle.
+     *
+     * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
+     */
+    private void dispatchCommand(int command) {
+        mTestedSm.sendMessage(command);
+        mLooper.dispatchAll();
+    }
+
+    /**
+     * Special override to tell the state machine that the upstream interface has changed.
+     *
+     * @see #dispatchCommand(int)
+     * @param upstreamIface String name of upstream interface (or null)
+     */
+    private void dispatchTetherConnectionChanged(String upstreamIface) {
+        mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
+                upstreamIface);
+        mLooper.dispatchAll();
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
new file mode 100644
index 0000000..69152d4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -0,0 +1,1587 @@
+/*
+ * 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 static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.cloneShortcutList;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.app.IUidObserver;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+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;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.content.pm.ShortcutServiceInternal;
+import android.content.pm.Signature;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.test.InstrumentationTestCase;
+import android.test.mock.MockContext;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
+
+import org.junit.Assert;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+
+public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
+    protected static final String TAG = "ShortcutManagerTest";
+
+    /**
+     * Whether to enable dump or not.  Should be only true when debugging to avoid bugs where
+     * dump affecting the behavior.
+     */
+    protected static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
+
+    protected static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
+
+    protected static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
+
+    // public for mockito
+    public class BaseContext extends MockContext {
+        @Override
+        public Object getSystemService(String name) {
+            switch (name) {
+                case Context.USER_SERVICE:
+                    return mMockUserManager;
+            }
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getSystemServiceName(Class<?> serviceClass) {
+            return getTestContext().getSystemServiceName(serviceClass);
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mMockPackageManager;
+        }
+
+        @Override
+        public Resources getResources() {
+            return getTestContext().getResources();
+        }
+
+        @Override
+        public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+                IntentFilter filter, String broadcastPermission, Handler scheduler) {
+            // ignore.
+            return null;
+        }
+    }
+
+    /** Context used in the client side */
+    public class ClientContext extends BaseContext {
+        @Override
+        public String getPackageName() {
+            return mInjectedClientPackage;
+        }
+
+        @Override
+        public int getUserId() {
+            return getCallingUserId();
+        }
+    }
+
+    /** Context used in the service side */
+    public class ServiceContext extends BaseContext {
+        long injectClearCallingIdentity() {
+            final int prevCallingUid = mInjectedCallingUid;
+            mInjectedCallingUid = Process.SYSTEM_UID;
+            return prevCallingUid;
+        }
+
+        void injectRestoreCallingIdentity(long token) {
+            mInjectedCallingUid = (int) token;
+        }
+
+        @Override
+        public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
+                UserHandle userId) {
+        }
+
+        @Override
+        public int getUserId() {
+            return UserHandle.USER_SYSTEM;
+        }
+
+        public PackageInfo injectGetActivitiesWithMetadata(
+                String packageName, @UserIdInt int userId) {
+            return BaseShortcutManagerTest.this.injectGetActivitiesWithMetadata(packageName, userId);
+        }
+
+        public XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+            return BaseShortcutManagerTest.this.injectXmlMetaData(activityInfo, key);
+        }
+    }
+
+    /** ShortcutService with injection override methods. */
+    protected final class ShortcutServiceTestable extends ShortcutService {
+        final ServiceContext mContext;
+        IUidObserver mUidObserver;
+
+        public ShortcutServiceTestable(ServiceContext context, Looper looper) {
+            super(context, looper);
+            mContext = context;
+        }
+
+        @Override
+        boolean injectShouldPerformVerification() {
+            return true; // Always verify during unit tests.
+        }
+
+        @Override
+        String injectShortcutManagerConstants() {
+            return ConfigConstants.KEY_RESET_INTERVAL_SEC + "=" + (INTERVAL / 1000) + ","
+                    + ConfigConstants.KEY_MAX_SHORTCUTS + "=" + MAX_SHORTCUTS + ","
+                    + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "="
+                    + MAX_UPDATES_PER_INTERVAL + ","
+                    + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=" + MAX_ICON_DIMENSION + ","
+                    + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "="
+                    + MAX_ICON_DIMENSION_LOWRAM + ","
+                    + ConfigConstants.KEY_ICON_FORMAT + "=PNG,"
+                    + ConfigConstants.KEY_ICON_QUALITY + "=100";
+        }
+
+        @Override
+        long injectClearCallingIdentity() {
+            return mContext.injectClearCallingIdentity();
+        }
+
+        @Override
+        void injectRestoreCallingIdentity(long token) {
+            mContext.injectRestoreCallingIdentity(token);
+        }
+
+        @Override
+        int injectDipToPixel(int dip) {
+            return dip;
+        }
+
+        @Override
+        long injectCurrentTimeMillis() {
+            return mInjectedCurrentTimeMillis;
+        }
+
+        @Override
+        long injectElapsedRealtime() {
+            // TODO This should be kept separately from mInjectedCurrentTimeMillis, since
+            // this should increase even if we rewind mInjectedCurrentTimeMillis in some tests.
+            return mInjectedCurrentTimeMillis - START_TIME;
+        }
+
+        @Override
+        int injectBinderCallingUid() {
+            return mInjectedCallingUid;
+        }
+
+        @Override
+        int injectGetPackageUid(String packageName, int userId) {
+            return getInjectedPackageInfo(packageName, userId, false).applicationInfo.uid;
+        }
+
+        @Override
+        File injectSystemDataPath() {
+            return new File(mInjectedFilePathRoot, "system");
+        }
+
+        @Override
+        File injectUserDataPath(@UserIdInt int userId) {
+            return new File(mInjectedFilePathRoot, "user-" + userId);
+        }
+
+        @Override
+        void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
+            // Can't check
+        }
+
+        @Override
+        boolean injectIsLowRamDevice() {
+            return mInjectedIsLowRamDevice;
+        }
+
+        @Override
+        void injectRegisterUidObserver(IUidObserver observer, int which) {
+            mUidObserver = observer;
+        }
+
+        @Override
+        PackageManagerInternal injectPackageManagerInternal() {
+            return mMockPackageManagerInternal;
+        }
+
+        @Override
+        boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
+            return mDefaultLauncherChecker.test(callingPackage, userId);
+        }
+
+        @Override
+        PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
+                boolean getSignatures) {
+            return getInjectedPackageInfo(packageName, userId, getSignatures);
+        }
+
+        @Override
+        ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
+            PackageInfo pi = injectPackageInfo(packageName, userId, /* getSignatures= */ false);
+            return pi != null ? pi.applicationInfo : null;
+        }
+
+        @Override
+        List<PackageInfo> injectInstalledPackages(@UserIdInt int userId) {
+            return getInstalledPackages(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 injectPostToHandler(Runnable r) {
+            final long token = mContext.injectClearCallingIdentity();
+            r.run();
+            mContext.injectRestoreCallingIdentity(token);
+        }
+
+        @Override
+        void injectEnforceCallingPermission(String permission, String message) {
+            if (!mCallerPermissions.contains(permission)) {
+                throw new SecurityException("Missing permission: " + permission);
+            }
+        }
+
+        @Override
+        void wtf(String message, Exception e) {
+            // During tests, WTF is fatal.
+            fail(message + "  exception: " + e);
+        }
+    }
+
+    /** ShortcutManager with injection override methods. */
+    protected class ShortcutManagerTestable extends ShortcutManager {
+        public ShortcutManagerTestable(Context context, ShortcutServiceTestable service) {
+            super(context, service);
+        }
+
+        @Override
+        protected int injectMyUserId() {
+            return UserHandle.getUserId(mInjectedCallingUid);
+        }
+
+        @Override
+        public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+            // Note to simulate the binder RPC, we need to clone the incoming arguments.
+            // Otherwise bad things will happen because they're mutable.
+            return super.setDynamicShortcuts(cloneShortcutList(shortcutInfoList));
+        }
+
+        @Override
+        public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+            // Note to simulate the binder RPC, we need to clone the incoming arguments.
+            return super.addDynamicShortcuts(cloneShortcutList(shortcutInfoList));
+        }
+
+        @Override
+        public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) {
+            // Note to simulate the binder RPC, we need to clone the incoming arguments.
+            return super.updateShortcuts(cloneShortcutList(shortcutInfoList));
+        }
+    }
+
+    protected class LauncherAppImplTestable extends LauncherAppsImpl {
+        final ServiceContext mContext;
+
+        public LauncherAppImplTestable(ServiceContext context) {
+            super(context);
+            mContext = context;
+        }
+
+        @Override
+        public void verifyCallingPackage(String callingPackage) {
+            // SKIP
+        }
+
+        @Override
+        void postToPackageMonitorHandler(Runnable r) {
+            final long token = mContext.injectClearCallingIdentity();
+            r.run();
+            mContext.injectRestoreCallingIdentity(token);
+        }
+
+        @Override
+        int injectBinderCallingUid() {
+            return mInjectedCallingUid;
+        }
+
+        @Override
+        long injectClearCallingIdentity() {
+            final int prevCallingUid = mInjectedCallingUid;
+            mInjectedCallingUid = Process.SYSTEM_UID;
+            return prevCallingUid;
+        }
+
+        @Override
+        void injectRestoreCallingIdentity(long token) {
+            mInjectedCallingUid = (int) token;
+        }
+    }
+
+    protected class LauncherAppsTestable extends LauncherApps {
+        public LauncherAppsTestable(Context context, ILauncherApps service) {
+            super(context, service);
+        }
+    }
+
+    public static class ShortcutActivity extends Activity {
+    }
+
+    public static class ShortcutActivity2 extends Activity {
+    }
+
+    public static class ShortcutActivity3 extends Activity {
+    }
+
+    protected ServiceContext mServiceContext;
+    protected ClientContext mClientContext;
+
+    protected ShortcutServiceTestable mService;
+    protected ShortcutManagerTestable mManager;
+    protected ShortcutServiceInternal mInternal;
+
+    protected LauncherAppImplTestable mLauncherAppImpl;
+
+    // LauncherApps has per-instace state, so we need a differnt instance for each launcher.
+    protected final Map<Pair<Integer, String>, LauncherAppsTestable>
+            mLauncherAppsMap = new HashMap<>();
+    protected LauncherAppsTestable mLauncherApps; // Current one
+
+    protected File mInjectedFilePathRoot;
+
+    protected long mInjectedCurrentTimeMillis;
+
+    protected boolean mInjectedIsLowRamDevice;
+
+    protected Locale mInjectedLocale = Locale.ENGLISH;
+
+    protected int mInjectedCallingUid;
+    protected String mInjectedClientPackage;
+
+    protected Map<String, PackageInfo> mInjectedPackages;
+
+    protected Set<PackageWithUser> mUninstalledPackages;
+
+    protected PackageManager mMockPackageManager;
+    protected PackageManagerInternal mMockPackageManagerInternal;
+    protected UserManager mMockUserManager;
+    protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
+
+    protected static final String CALLING_PACKAGE_1 = "com.android.test.1";
+    protected static final int CALLING_UID_1 = 10001;
+
+    protected static final String CALLING_PACKAGE_2 = "com.android.test.2";
+    protected static final int CALLING_UID_2 = 10002;
+
+    protected static final String CALLING_PACKAGE_3 = "com.android.test.3";
+    protected static final int CALLING_UID_3 = 10003;
+
+    protected static final String CALLING_PACKAGE_4 = "com.android.test.4";
+    protected static final int CALLING_UID_4 = 10004;
+
+    protected static final String LAUNCHER_1 = "com.android.launcher.1";
+    protected static final int LAUNCHER_UID_1 = 10011;
+
+    protected static final String LAUNCHER_2 = "com.android.launcher.2";
+    protected static final int LAUNCHER_UID_2 = 10012;
+
+    protected static final String LAUNCHER_3 = "com.android.launcher.3";
+    protected static final int LAUNCHER_UID_3 = 10013;
+
+    protected static final String LAUNCHER_4 = "com.android.launcher.4";
+    protected static final int LAUNCHER_UID_4 = 10014;
+
+    protected static final int USER_0 = UserHandle.USER_SYSTEM;
+    protected static final int USER_10 = 10;
+    protected static final int USER_11 = 11;
+    protected static final int USER_P0 = 20; // profile of user 0
+
+    protected static final UserHandle HANDLE_USER_0 = UserHandle.of(USER_0);
+    protected static final UserHandle HANDLE_USER_10 = UserHandle.of(USER_10);
+    protected static final UserHandle HANDLE_USER_11 = UserHandle.of(USER_11);
+    protected static final UserHandle HANDLE_USER_P0 = UserHandle.of(USER_P0);
+
+    protected static final UserInfo USER_INFO_0 = withProfileGroupId(
+            new UserInfo(USER_0, "user0",
+                    UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED), 10);
+
+    protected static final UserInfo USER_INFO_10 =
+            new UserInfo(USER_10, "user10", UserInfo.FLAG_INITIALIZED);
+
+    protected static final UserInfo USER_INFO_11 =
+            new UserInfo(USER_11, "user11", UserInfo.FLAG_INITIALIZED);
+
+    protected static final UserInfo USER_INFO_P0 = withProfileGroupId(
+            new UserInfo(USER_P0, "userP0",
+                    UserInfo.FLAG_MANAGED_PROFILE), 10);
+
+    protected BiPredicate<String, Integer> mDefaultLauncherChecker =
+            (callingPackage, userId) ->
+            LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
+            || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage);
+
+    protected static final long START_TIME = 1440000000101L;
+
+    protected static final long INTERVAL = 10000;
+
+    protected static final int MAX_SHORTCUTS = 10;
+
+    protected static final int MAX_UPDATES_PER_INTERVAL = 3;
+
+    protected static final int MAX_ICON_DIMENSION = 128;
+
+    protected static final int MAX_ICON_DIMENSION_LOWRAM = 32;
+
+    protected static final ShortcutQuery QUERY_ALL = new ShortcutQuery();
+
+    protected final ArrayList<String> mCallerPermissions = new ArrayList<>();
+
+    protected final HashMap<String, LinkedHashMap<ComponentName, Integer>> mActivityMetadataResId
+            = new HashMap<>();
+
+    static {
+        QUERY_ALL.setQueryFlags(
+                ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mServiceContext = spy(new ServiceContext());
+        mClientContext = new ClientContext();
+
+        mMockPackageManager = mock(PackageManager.class);
+        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockUserManager = mock(UserManager.class);
+        mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+        LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+        LocalServices.addService(UsageStatsManagerInternal.class, mMockUsageStatsManagerInternal);
+
+        // Prepare injection values.
+
+        mInjectedCurrentTimeMillis = START_TIME;
+
+        mInjectedPackages = new HashMap<>();
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1);
+        addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2);
+        addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3);
+        addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10);
+        addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4);
+        addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5);
+        addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6);
+        addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10);
+
+        // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target.
+        updatePackageInfo(CALLING_PACKAGE_3,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+        updatePackageInfo(LAUNCHER_3,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+        mUninstalledPackages = new HashSet<>();
+
+        mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
+
+        deleteAllSavedFiles();
+
+        // Set up users.
+        doAnswer(inv -> {
+                assertSystem();
+                return USER_INFO_0;
+        }).when(mMockUserManager).getUserInfo(eq(USER_0));
+
+        doAnswer(inv -> {
+                assertSystem();
+                return USER_INFO_10;
+        }).when(mMockUserManager).getUserInfo(eq(USER_10));
+
+        doAnswer(inv -> {
+                assertSystem();
+                return USER_INFO_11;
+        }).when(mMockUserManager).getUserInfo(eq(USER_11));
+
+        doAnswer(inv -> {
+                assertSystem();
+                return USER_INFO_P0;
+        }).when(mMockUserManager).getUserInfo(eq(USER_P0));
+
+        // User 0 is always running.
+        when(mMockUserManager.isUserRunning(eq(USER_0))).thenAnswer(new AnswerIsUserRunning(true));
+
+        setUpAppResources();
+
+        // Start the service.
+        initService();
+        setCaller(CALLING_PACKAGE_1);
+
+        // In order to complicate the situation, we set mLocaleChangeSequenceNumber to 1 by
+        // calling this.  Running test with mLocaleChangeSequenceNumber == 0 might make us miss
+        // some edge cases.
+        mInternal.onSystemLocaleChangedNoLock();
+    }
+
+    /**
+     * Returns a boolean but also checks if the current UID is SYSTEM_UID.
+     */
+    protected class AnswerIsUserRunning implements Answer<Boolean> {
+        protected final boolean mAnswer;
+
+        protected AnswerIsUserRunning(boolean answer) {
+            mAnswer = answer;
+        }
+
+        @Override
+        public Boolean answer(InvocationOnMock invocation) throws Throwable {
+            assertEquals("isUserRunning() must be called on SYSTEM UID.",
+                    Process.SYSTEM_UID, mInjectedCallingUid);
+            return mAnswer;
+        }
+    }
+
+    protected void setUpAppResources() throws Exception {
+        setUpAppResources(/* offset = */ 0);
+    }
+
+    protected void setUpAppResources(int ressIdOffset) throws Exception {
+        // ressIdOffset is used to adjust resource IDs to emulate the case where an updated app
+        // has resource IDs changed.
+
+        doAnswer(pmInvocation -> {
+            assertEquals(Process.SYSTEM_UID, mInjectedCallingUid);
+
+            final String packageName = (String) pmInvocation.getArguments()[0];
+            final int userId = (Integer) pmInvocation.getArguments()[1];
+
+            final Resources res = mock(Resources.class);
+
+            doAnswer(resInvocation -> {
+                final int argResId = (Integer) resInvocation.getArguments()[0];
+
+                return "string-" + packageName + "-user:" + userId + "-res:" + argResId
+                        + "/" + mInjectedLocale;
+            }).when(res).getString(anyInt());
+
+            doAnswer(resInvocation -> {
+                final int resId = (Integer) resInvocation.getArguments()[0];
+
+                // Always use the "string" resource type.  The type doesn't matter during the test.
+                return packageName + ":string/r" + resId;
+            }).when(res).getResourceName(anyInt());
+
+            doAnswer(resInvocation -> {
+                final String argResName = (String) resInvocation.getArguments()[0];
+                final String argType = (String) resInvocation.getArguments()[1];
+                final String argPackageName = (String) resInvocation.getArguments()[2];
+
+                // See the above code.  getResourceName() will just use "r" + res ID as the entry
+                // name.
+                String entryName = argResName;
+                if (entryName.contains("/")) {
+                    entryName = ShortcutInfo.getResourceEntryName(entryName);
+                }
+                return Integer.parseInt(entryName.substring(1)) + ressIdOffset;
+            }).when(res).getIdentifier(anyString(), anyString(), anyString());
+            return res;
+        }).when(mMockPackageManager).getResourcesForApplicationAsUser(anyString(), anyInt());
+    }
+
+    protected static UserInfo withProfileGroupId(UserInfo in, int groupId) {
+        in.profileGroupId = groupId;
+        return in;
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (DUMP_IN_TEARDOWN) dumpsysOnLogcat("Teardown");
+
+        shutdownServices();
+
+        super.tearDown();
+    }
+
+    protected Context getTestContext() {
+        return getInstrumentation().getContext();
+    }
+
+    protected ShortcutManager getManager() {
+        return mManager;
+    }
+
+    protected void deleteAllSavedFiles() {
+        // Empty the data directory.
+        if (mInjectedFilePathRoot.exists()) {
+            Assert.assertTrue("failed to delete dir",
+                    FileUtils.deleteContents(mInjectedFilePathRoot));
+        }
+        mInjectedFilePathRoot.mkdirs();
+    }
+
+    /** (Re-) init the manager and the service. */
+    protected void initService() {
+        shutdownServices();
+
+        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+
+        // Instantiate targets.
+        mService = new ShortcutServiceTestable(mServiceContext, Looper.getMainLooper());
+        mManager = new ShortcutManagerTestable(mClientContext, mService);
+
+        mInternal = LocalServices.getService(ShortcutServiceInternal.class);
+
+        mLauncherAppImpl = new LauncherAppImplTestable(mServiceContext);
+        mLauncherApps = null;
+        mLauncherAppsMap.clear();
+
+        // Send boot sequence events.
+        mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY);
+
+        // Make sure a call to onSystemLocaleChangedNoLock() before PHASE_BOOT_COMPLETED will be
+        // ignored.
+        final long origSequenceNumber = mService.getLocaleChangeSequenceNumber();
+        mInternal.onSystemLocaleChangedNoLock();
+        assertEquals(origSequenceNumber, mService.getLocaleChangeSequenceNumber());
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+    }
+
+    protected void shutdownServices() {
+        if (mService != null) {
+            // Flush all the unsaved data from the previous instance.
+            mService.saveDirtyInfo();
+
+            // Make sure everything is consistent.
+            mService.verifyStates();
+        }
+        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+
+        mService = null;
+        mManager = null;
+        mInternal = null;
+        mLauncherAppImpl = null;
+        mLauncherApps = null;
+        mLauncherAppsMap.clear();
+    }
+
+    protected void addPackage(String packageName, int uid, int version) {
+        addPackage(packageName, uid, version, packageName);
+    }
+
+    protected Signature[] genSignatures(String... signatures) {
+        final Signature[] sigs = new Signature[signatures.length];
+        for (int i = 0; i < signatures.length; i++){
+            sigs[i] = new Signature(signatures[i].getBytes());
+        }
+        return sigs;
+    }
+
+    protected PackageInfo genPackage(String packageName, int uid, int version, String... signatures) {
+        final PackageInfo pi = new PackageInfo();
+        pi.packageName = packageName;
+        pi.applicationInfo = new ApplicationInfo();
+        pi.applicationInfo.uid = uid;
+        pi.applicationInfo.flags = ApplicationInfo.FLAG_INSTALLED
+                | ApplicationInfo.FLAG_ALLOW_BACKUP;
+        pi.versionCode = version;
+        pi.applicationInfo.versionCode = version;
+        pi.signatures = genSignatures(signatures);
+
+        return pi;
+    }
+
+    protected void addPackage(String packageName, int uid, int version, String... signatures) {
+        mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures));
+    }
+
+    protected void updatePackageInfo(String packageName, Consumer<PackageInfo> c) {
+        c.accept(mInjectedPackages.get(packageName));
+    }
+
+    protected void updatePackageVersion(String packageName, int increment) {
+        updatePackageInfo(packageName, pi -> {
+            pi.versionCode += increment;
+            pi.applicationInfo.versionCode += increment;
+        });
+    }
+
+    protected void updatePackageLastUpdateTime(String packageName, long increment) {
+        updatePackageInfo(packageName, pi -> {
+            pi.lastUpdateTime += increment;
+        });
+    }
+
+    protected void uninstallPackage(int userId, String packageName) {
+        if (ENABLE_DUMP) {
+            Log.v(TAG, "Unnstall package " + packageName + " / " + userId);
+        }
+        mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
+    }
+
+    protected void installPackage(int userId, String packageName) {
+        if (ENABLE_DUMP) {
+            Log.v(TAG, "Install package " + packageName + " / " + userId);
+        }
+        mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
+    }
+
+    PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
+            boolean getSignatures) {
+        final PackageInfo pi = mInjectedPackages.get(packageName);
+        if (pi == null) return null;
+
+        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;
+        }
+
+        if (getSignatures) {
+            ret.signatures = pi.signatures;
+        }
+
+        return ret;
+    }
+
+    protected void addApplicationInfo(PackageInfo pi, List<ApplicationInfo> list) {
+        if (pi != null && pi.applicationInfo != null) {
+            list.add(pi.applicationInfo);
+        }
+    }
+
+    protected 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 addPackageInfo(PackageInfo pi, List<PackageInfo> list) {
+        if (pi != null) {
+            list.add(pi);
+        }
+    }
+
+    private List<PackageInfo> getInstalledPackages(int userId) {
+        final ArrayList<PackageInfo> ret = new ArrayList<>();
+
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret);
+        addPackageInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret);
+
+        return ret;
+    }
+
+    protected void addManifestShortcutResource(ComponentName activity, int resId) {
+        final String packageName = activity.getPackageName();
+        LinkedHashMap<ComponentName, Integer> map = mActivityMetadataResId.get(packageName);
+        if (map == null) {
+            map = new LinkedHashMap<>();
+            mActivityMetadataResId.put(packageName, map);
+        }
+        map.put(activity, resId);
+    }
+
+    protected 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;
+    }
+
+    protected 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 */
+    protected void setCaller(String packageName, int userId) {
+        mInjectedClientPackage = packageName;
+        mInjectedCallingUid =
+                Preconditions.checkNotNull(getInjectedPackageInfo(packageName, userId, false),
+                        "Unknown package").applicationInfo.uid;
+
+        // Set up LauncherApps for this caller.
+        final Pair<Integer, String> key = Pair.create(userId, packageName);
+        if (!mLauncherAppsMap.containsKey(key)) {
+            mLauncherAppsMap.put(key, new LauncherAppsTestable(mClientContext, mLauncherAppImpl));
+        }
+        mLauncherApps = mLauncherAppsMap.get(key);
+    }
+
+    protected void setCaller(String packageName) {
+        setCaller(packageName, UserHandle.USER_SYSTEM);
+    }
+
+    protected String getCallingPackage() {
+        return mInjectedClientPackage;
+    }
+
+    protected void setDefaultLauncherChecker(BiPredicate<String, Integer> p) {
+        mDefaultLauncherChecker = p;
+    }
+
+    protected void runWithCaller(String packageName, int userId, Runnable r) {
+        final String previousPackage = mInjectedClientPackage;
+        final int previousUserId = UserHandle.getUserId(mInjectedCallingUid);
+
+        setCaller(packageName, userId);
+
+        r.run();
+
+        setCaller(previousPackage, previousUserId);
+    }
+
+    protected void runWithSystemUid(Runnable r) {
+        final int origUid = mInjectedCallingUid;
+        mInjectedCallingUid = Process.SYSTEM_UID;
+        r.run();
+        mInjectedCallingUid = origUid;
+    }
+
+    protected void lookupAndFillInResourceNames(ShortcutInfo si) {
+        runWithSystemUid(() -> si.lookupAndFillInResourceNames(
+                mService.injectGetResourcesForApplicationAsUser(si.getPackage(), si.getUserId())));
+    }
+
+    protected int getCallingUserId() {
+        return UserHandle.getUserId(mInjectedCallingUid);
+    }
+
+    protected UserHandle getCallingUser() {
+        return UserHandle.of(getCallingUserId());
+    }
+
+    /** For debugging */
+    protected void dumpsysOnLogcat() {
+        dumpsysOnLogcat("");
+    }
+
+    protected void dumpsysOnLogcat(String message) {
+        dumpsysOnLogcat(message, false);
+    }
+
+    protected void dumpsysOnLogcat(String message, boolean force) {
+        if (force || !ENABLE_DUMP) return;
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        final PrintWriter pw = new PrintWriter(out);
+        mService.dumpInner(pw, null);
+        pw.close();
+
+        Log.v(TAG, "Dumping ShortcutService: " + message);
+        for (String line : out.toString().split("\n")) {
+            Log.v(TAG, line);
+        }
+    }
+
+    /**
+     * For debugging, dump arbitrary file on logcat.
+     */
+    protected void dumpFileOnLogcat(String path) {
+        dumpFileOnLogcat(path, "");
+    }
+
+    protected void dumpFileOnLogcat(String path, String message) {
+        if (!ENABLE_DUMP) return;
+
+        Log.v(TAG, "Dumping file: " + path + " " + message);
+        final StringBuilder sb = new StringBuilder();
+        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                Log.v(TAG, line);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Couldn't read file", e);
+            fail("Exception " + e);
+        }
+    }
+
+    /**
+     * For debugging, dump the main state file on logcat.
+     */
+    protected void dumpBaseStateFile() {
+        mService.saveDirtyInfo();
+        dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
+                + "/system/" + ShortcutService.FILENAME_BASE_STATE);
+    }
+
+    /**
+     * For debugging, dump per-user state file on logcat.
+     */
+    protected void dumpUserFile(int userId) {
+        dumpUserFile(userId, "");
+    }
+
+    protected void dumpUserFile(int userId, String message) {
+        mService.saveDirtyInfo();
+        dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
+                + "/user-" + userId
+                + "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
+    }
+
+    protected void waitOnMainThread() throws Throwable {
+        runTestOnUiThread(() -> {});
+    }
+
+    /**
+     * Make a shortcut with an ID.
+     */
+    protected ShortcutInfo makeShortcut(String id) {
+        return makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    protected ShortcutInfo makeShortcutWithTitle(String id, String title) {
+        return makeShortcut(
+                id, title, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    /**
+     * Make a shortcut with an ID and timestamp.
+     */
+    protected ShortcutInfo makeShortcutWithTimestamp(String id, long timestamp) {
+        final ShortcutInfo s = makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+        s.setTimestamp(timestamp);
+        return s;
+    }
+
+    /**
+     * Make a shortcut with an ID, a timestamp and an activity component
+     */
+    protected ShortcutInfo makeShortcutWithTimestampWithActivity(String id, long timestamp,
+            ComponentName activity) {
+        final ShortcutInfo s = makeShortcut(
+                id, "Title-" + id, activity, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+        s.setTimestamp(timestamp);
+        return s;
+    }
+
+    /**
+     * Make a shortcut with an ID and icon.
+     */
+    protected ShortcutInfo makeShortcutWithIcon(String id, Icon icon) {
+        return makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, icon,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    protected ShortcutInfo makePackageShortcut(String packageName, String id) {
+        String origCaller = getCallingPackage();
+
+        setCaller(packageName);
+        ShortcutInfo s = makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+        setCaller(origCaller); // restore the caller
+
+        return s;
+    }
+
+    /**
+     * Make multiple shortcuts with IDs.
+     */
+    protected List<ShortcutInfo> makeShortcuts(String... ids) {
+        final ArrayList<ShortcutInfo> ret = new ArrayList();
+        for (String id : ids) {
+            ret.add(makeShortcut(id));
+        }
+        return ret;
+    }
+
+    protected ShortcutInfo.Builder makeShortcutBuilder() {
+        return new ShortcutInfo.Builder(mClientContext);
+    }
+
+    protected ShortcutInfo makeShortcutWithActivity(String id, ComponentName activity) {
+        return makeShortcut(
+                id, "Title-" + id, activity, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    protected ShortcutInfo makeShortcutWithActivityAndTitle(String id, ComponentName activity,
+            String title) {
+        return makeShortcut(
+                id, title, activity, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    protected ShortcutInfo makeShortcutWithActivityAndRank(String id, ComponentName activity,
+            int rank) {
+        return makeShortcut(
+                id, "Title-" + id, activity, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank);
+    }
+
+    /**
+     * Make a shortcut with details.
+     */
+    protected ShortcutInfo makeShortcut(String id, String title, ComponentName activity,
+            Icon icon, Intent intent, int rank) {
+        final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext)
+                .setId(id)
+                .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy"))
+                .setTitle(title)
+                .setRank(rank)
+                .setIntent(intent);
+        if (icon != null) {
+            b.setIcon(icon);
+        }
+        if (activity != null) {
+            b.setActivity(activity);
+        }
+        final ShortcutInfo s = b.build();
+
+        s.setTimestamp(mInjectedCurrentTimeMillis); // HACK
+
+        return s;
+    }
+
+    /**
+     * Make an intent.
+     */
+    protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
+        final Intent intent = new Intent(action);
+        intent.setComponent(makeComponent(clazz));
+        intent.replaceExtras(makeBundle(bundleKeysAndValues));
+        return intent;
+    }
+
+    /**
+     * Make an component name, with the client context.
+     */
+    @NonNull
+    protected ComponentName makeComponent(Class<?> clazz) {
+        return new ComponentName(mClientContext, clazz);
+    }
+
+    @NonNull
+    protected ShortcutInfo findById(List<ShortcutInfo> list, String id) {
+        for (ShortcutInfo s : list) {
+            if (s.getId().equals(id)) {
+                return s;
+            }
+        }
+        fail("Shortcut with id " + id + " not found");
+        return null;
+    }
+
+    protected void assertSystem() {
+        assertEquals("Caller must be system", Process.SYSTEM_UID, mInjectedCallingUid);
+    }
+
+    protected void assertResetTimes(long expectedLastResetTime, long expectedNextResetTime) {
+        assertEquals(expectedLastResetTime, mService.getLastResetTimeLocked());
+        assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked());
+    }
+
+    public static List<ShortcutInfo> assertAllNotHaveIcon(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNull("ID " + s.getId(), s.getIcon());
+        }
+        return actualShortcuts;
+    }
+
+    @NonNull
+    protected List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts,
+            int shortcutFlags) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId() + " doesn't have flags " + shortcutFlags,
+                    s.hasFlags(shortcutFlags));
+        }
+        return actualShortcuts;
+    }
+
+    protected ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) {
+        return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
+    }
+
+    protected void assertShortcutExists(String packageName, String shortcutId, int userId) {
+        assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
+    }
+
+    protected void assertShortcutNotExists(String packageName, String shortcutId, int userId) {
+        assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null);
+    }
+
+    protected Intent launchShortcutAndGetIntent(
+            @NonNull String packageName, @NonNull String shortcutId, int userId) {
+        reset(mServiceContext);
+        assertTrue(mLauncherApps.startShortcut(packageName, shortcutId, null, null,
+                UserHandle.of(userId)));
+
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mServiceContext).startActivityAsUser(
+                intentCaptor.capture(),
+                any(Bundle.class),
+                eq(UserHandle.of(userId)));
+        return intentCaptor.getValue();
+    }
+
+    protected Intent launchShortcutAndGetIntent_withShortcutInfo(
+            @NonNull String packageName, @NonNull String shortcutId, int userId) {
+        reset(mServiceContext);
+
+        assertTrue(mLauncherApps.startShortcut(
+                getShortcutInfoAsLauncher(packageName, shortcutId, userId), null, null));
+
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mServiceContext).startActivityAsUser(
+                intentCaptor.capture(),
+                any(Bundle.class),
+                eq(UserHandle.of(userId)));
+        return intentCaptor.getValue();
+    }
+
+    protected void assertShortcutLaunchable(@NonNull String packageName, @NonNull String shortcutId,
+            int userId) {
+        assertNotNull(launchShortcutAndGetIntent(packageName, shortcutId, userId));
+        assertNotNull(launchShortcutAndGetIntent_withShortcutInfo(packageName, shortcutId, userId));
+    }
+
+    protected void assertShortcutNotLaunchable(@NonNull String packageName,
+            @NonNull String shortcutId, int userId) {
+        try {
+            final boolean ok = mLauncherApps.startShortcut(packageName, shortcutId, null, null,
+                    UserHandle.of(userId));
+            if (!ok) {
+                return; // didn't launch, okay.
+            }
+            fail();
+        } catch (SecurityException expected) {
+            // security exception is okay too.
+        }
+    }
+
+    protected void assertBitmapDirectories(int userId, String... expectedDirectories) {
+        final Set<String> expected = hashSet(set(expectedDirectories));
+
+        final Set<String> actual = new HashSet<>();
+
+        final File[] files = mService.getUserBitmapFilePath(userId).listFiles();
+        if (files != null) {
+            for (File child : files) {
+                if (child.isDirectory()) {
+                    actual.add(child.getName());
+                }
+            }
+        }
+
+        assertEquals(expected, actual);
+    }
+
+    protected void assertBitmapFiles(int userId, String packageName, String... expectedFiles) {
+        final Set<String> expected = hashSet(set(expectedFiles));
+
+        final Set<String> actual = new HashSet<>();
+
+        final File[] files = new File(mService.getUserBitmapFilePath(userId), packageName)
+                .listFiles();
+        if (files != null) {
+            for (File child : files) {
+                if (child.isFile()) {
+                    actual.add(child.getName());
+                }
+            }
+        }
+
+        assertEquals(expected, actual);
+    }
+
+    protected String getBitmapFilename(int userId, String packageName, String shortcutId) {
+        final ShortcutInfo si = mService.getPackageShortcutForTest(packageName, shortcutId, userId);
+        if (si == null) {
+            return null;
+        }
+        return new File(si.getBitmapPath()).getName();
+    }
+
+    protected List<ShortcutInfo> getCallerShortcuts() {
+        final ShortcutPackage p = mService.getPackageShortcutForTest(
+                getCallingPackage(), getCallingUserId());
+        return p == null ? null : p.getAllShortcutsForTest();
+    }
+
+    protected ShortcutInfo getCallerShortcut(String shortcutId) {
+        return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
+    }
+
+    protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
+        final List<ShortcutInfo>[] ret = new List[1];
+        runWithCaller(launcher, userId, () -> {
+            final ShortcutQuery q = new ShortcutQuery();
+            q.setQueryFlags(queryFlags);
+            ret[0] = mLauncherApps.getShortcuts(q, UserHandle.of(userId));
+        });
+        return ret[0];
+    }
+
+    protected List<ShortcutInfo> getLauncherPinnedShortcuts(String launcher, int userId) {
+        return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED);
+    }
+
+    protected ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId,
+            int userId) {
+        final List<ShortcutInfo> infoList =
+                mLauncherApps.getShortcutInfo(packageName, list(shortcutId),
+                        UserHandle.of(userId));
+        assertEquals("No shortcutInfo found (or too many of them)", 1, infoList.size());
+        return infoList.get(0);
+    }
+
+    protected 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;
+    }
+
+    protected Intent genPackageDeleteIntent(String pakcageName, int userId) {
+        Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        i.setData(Uri.parse("package:" + pakcageName));
+        i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        return i;
+    }
+
+    protected Intent genPackageUpdateIntent(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);
+        i.putExtra(Intent.EXTRA_REPLACING, true);
+        return i;
+    }
+
+    protected Intent genPackageDataClear(String packageName, int userId) {
+        Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        i.setData(Uri.parse("package:" + packageName));
+        i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        return i;
+    }
+
+    protected void assertExistsAndShadow(ShortcutPackageItem spi) {
+        assertNotNull(spi);
+        assertTrue(spi.getPackageInfo().isShadow());
+    }
+
+    protected File makeFile(File baseDirectory, String... paths) {
+        File ret = baseDirectory;
+
+        for (String path : paths) {
+            ret = new File(ret, path);
+        }
+
+        return ret;
+    }
+
+    protected boolean bitmapDirectoryExists(String packageName, int userId) {
+        final File path = new File(mService.getUserBitmapFilePath(userId), packageName);
+        return path.isDirectory();
+    }
+    protected static ShortcutQuery buildQuery(long changedSince,
+            String packageName, ComponentName componentName,
+            /* @ShortcutQuery.QueryFlags */ int flags) {
+        return buildQuery(changedSince, packageName, null, componentName, flags);
+    }
+
+    protected static ShortcutQuery buildQuery(long changedSince,
+            String packageName, List<String> shortcutIds, ComponentName componentName,
+            /* @ShortcutQuery.QueryFlags */ int flags) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setChangedSince(changedSince);
+        q.setPackage(packageName);
+        q.setShortcutIds(shortcutIds);
+        q.setActivity(componentName);
+        q.setQueryFlags(flags);
+        return q;
+    }
+
+    protected static ShortcutQuery buildAllQuery(String packageName) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setPackage(packageName);
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+        return q;
+    }
+
+    protected static ShortcutQuery buildPinnedQuery(String packageName) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setPackage(packageName);
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED);
+        return q;
+    }
+
+    protected void backupAndRestore() {
+        int prevUid = mInjectedCallingUid;
+
+        mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it.
+
+        dumpsysOnLogcat("Before backup");
+
+        final byte[] payload =  mService.getBackupPayload(USER_0);
+        if (ENABLE_DUMP) {
+            final String xml = new String(payload);
+            Log.v(TAG, "Backup payload:");
+            for (String line : xml.split("\n")) {
+                Log.v(TAG, line);
+            }
+        }
+
+        // Before doing anything else, uninstall all packages.
+        for (int userId : list(USER_0, USER_P0)) {
+            for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
+                    LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) {
+                uninstallPackage(userId, pkg);
+            }
+        }
+
+        shutdownServices();
+
+        deleteAllSavedFiles();
+
+        initService();
+        mService.applyRestore(payload, USER_0);
+
+        // handleUnlockUser will perform the gone package check, but it shouldn't remove
+        // shadow information.
+        mService.handleUnlockUser(USER_0);
+
+        dumpsysOnLogcat("After restore");
+
+        mInjectedCallingUid = prevUid;
+    }
+
+    protected void prepareCrossProfileDataSet() {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list()));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"),
+                    makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_3, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_4, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0);
+        });
+
+        // Launcher on a managed profile is referring ot user 0!
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"),
+                    HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"),
+                    HANDLE_USER_10);
+        });
+
+        // Then remove some dynamic shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list()));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"))));
+        });
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
new file mode 100644
index 0000000..de06047
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -0,0 +1,5821 @@
+/*
+ * 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 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;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBitmapSize;
+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.assertDynamicShortcutCountExceeded;
+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.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.filterByActivity;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.findShortcut;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.pfdToBitmap;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAll;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest.permission;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.frameworks.servicestests.R;
+import com.android.server.pm.ShortcutService.ConfigConstants;
+import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Tests for ShortcutService and ShortcutManager.
+ *
+ m FrameworksServicesTests &&
+ adb install \
+ -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest1 \
+ -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.
+ */
+@SmallTest
+public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
+
+    /**
+     * Test for the first launch path, no settings file available.
+     */
+    public void testFirstInitialize() {
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+    }
+
+    /**
+     * Test for {@link ShortcutService#getLastResetTimeLocked()} and
+     * {@link ShortcutService#getNextResetTimeLocked()}.
+     */
+    public void testUpdateAndGetNextResetTimeLocked() {
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+
+        // Advance clock.
+        mInjectedCurrentTimeMillis += 100;
+
+        // Shouldn't have changed.
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+
+        // Advance clock, almost the reset time.
+        mInjectedCurrentTimeMillis = START_TIME + INTERVAL - 1;
+
+        // Shouldn't have changed.
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+
+        // Advance clock.
+        mInjectedCurrentTimeMillis += 1;
+
+        assertResetTimes(START_TIME + INTERVAL, START_TIME + 2 * INTERVAL);
+
+        // Advance further; 4 hours since start.
+        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
+
+        assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
+    }
+
+    /**
+     * Test for the restoration from saved file.
+     */
+    public void testInitializeFromSavedFile() {
+
+        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
+        assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
+
+        mService.saveBaseStateLocked();
+
+        dumpBaseStateFile();
+
+        mService.saveDirtyInfo();
+
+        // Restore.
+        initService();
+
+        assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
+    }
+
+    /**
+     * Test for the restoration from restored file.
+     */
+    public void testLoadFromBrokenFile() {
+        // TODO Add various broken cases.
+    }
+
+    public void testLoadConfig() {
+        mService.updateConfigurationLocked(
+                ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123,"
+                        + ConfigConstants.KEY_MAX_SHORTCUTS + "=4,"
+                        + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=5,"
+                        + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100,"
+                        + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50,"
+                        + ConfigConstants.KEY_ICON_FORMAT + "=WEBP,"
+                        + ConfigConstants.KEY_ICON_QUALITY + "=75");
+        assertEquals(123000, mService.getResetIntervalForTest());
+        assertEquals(4, mService.getMaxDynamicShortcutsForTest());
+        assertEquals(5, mService.getMaxUpdatesPerIntervalForTest());
+        assertEquals(100, mService.getMaxIconDimensionForTest());
+        assertEquals(CompressFormat.WEBP, mService.getIconPersistFormatForTest());
+        assertEquals(75, mService.getIconPersistQualityForTest());
+
+        mInjectedIsLowRamDevice = true;
+        mService.updateConfigurationLocked(
+                ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100,"
+                        + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50,"
+                        + ConfigConstants.KEY_ICON_FORMAT + "=JPEG");
+        assertEquals(ShortcutService.DEFAULT_RESET_INTERVAL_SEC * 1000,
+                mService.getResetIntervalForTest());
+
+        assertEquals(ShortcutService.DEFAULT_MAX_SHORTCUTS_PER_APP,
+                mService.getMaxDynamicShortcutsForTest());
+
+        assertEquals(ShortcutService.DEFAULT_MAX_UPDATES_PER_INTERVAL,
+                mService.getMaxUpdatesPerIntervalForTest());
+
+        assertEquals(50, mService.getMaxIconDimensionForTest());
+
+        assertEquals(CompressFormat.JPEG, mService.getIconPersistFormatForTest());
+
+        assertEquals(ShortcutService.DEFAULT_ICON_PERSIST_QUALITY,
+                mService.getIconPersistQualityForTest());
+    }
+
+    // === Test for app side APIs ===
+
+    /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */
+    public void testGetMaxDynamicShortcutCount() {
+        assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity());
+    }
+
+    /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */
+    public void testGetRemainingCallCount() {
+        assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount());
+    }
+
+    public void testGetIconMaxDimensions() {
+        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());
+
+        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
+
+        assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime());
+    }
+
+    public void testSetDynamicShortcuts() {
+        setCaller(CALLING_PACKAGE_1, USER_0);
+
+        final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1);
+        final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.icon2));
+
+        final ShortcutInfo si1 = makeShortcut(
+                "shortcut1",
+                "Title 1",
+                makeComponent(ShortcutActivity.class),
+                icon1,
+                makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                        "key1", "val1", "nest", makeBundle("key", 123)),
+                /* weight */ 10);
+
+        final ShortcutInfo si2 = makeShortcut(
+                "shortcut2",
+                "Title 2",
+                /* activity */ null,
+                icon2,
+                makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                /* weight */ 12);
+        final ShortcutInfo si3 = makeShortcut("shortcut3");
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1", "shortcut2");
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        // TODO: Check fields
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1");
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(list()));
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        dumpsysOnLogcat();
+
+        mInjectedCurrentTimeMillis++; // Need to advance the clock for reset to work.
+        mService.resetThrottlingInner(UserHandle.USER_SYSTEM);
+
+        dumpsysOnLogcat();
+
+        assertTrue(mManager.setDynamicShortcuts(list(si2, si3)));
+        assertEquals(2, mManager.getDynamicShortcuts().size());
+
+        // TODO Check max number
+
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+        });
+    }
+
+    public void testAddDynamicShortcuts() {
+        setCaller(CALLING_PACKAGE_1, USER_0);
+
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+        final ShortcutInfo si2 = makeShortcut("shortcut2");
+        final ShortcutInfo si3 = makeShortcut("shortcut3");
+
+        assertEquals(3, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1");
+
+        assertTrue(mManager.addDynamicShortcuts(list(si2, si3)));
+        assertEquals(1, mManager.getRemainingCallCount());
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1", "shortcut2", "shortcut3");
+
+        // This should not crash.  It'll still consume the quota.
+        assertTrue(mManager.addDynamicShortcuts(list()));
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1", "shortcut2", "shortcut3");
+
+        mInjectedCurrentTimeMillis += INTERVAL; // reset
+
+        // Add with the same ID
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("shortcut1"))));
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1", "shortcut2", "shortcut3");
+
+        // TODO Check max number
+
+        // TODO Check fields.
+
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1"))));
+        });
+    }
+
+    public void testDeleteDynamicShortcuts() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+        final ShortcutInfo si2 = makeShortcut("shortcut2");
+        final ShortcutInfo si3 = makeShortcut("shortcut3");
+        final ShortcutInfo si4 = makeShortcut("shortcut4");
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3, si4)));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1", "shortcut2", "shortcut3", "shortcut4");
+
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mManager.removeDynamicShortcuts(list("shortcut1"));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut2", "shortcut3", "shortcut4");
+
+        mManager.removeDynamicShortcuts(list("shortcut1"));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut2", "shortcut3", "shortcut4");
+
+        mManager.removeDynamicShortcuts(list("shortcutXXX"));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut2", "shortcut3", "shortcut4");
+
+        mManager.removeDynamicShortcuts(list("shortcut2", "shortcut4"));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut3");
+
+        mManager.removeDynamicShortcuts(list("shortcut3"));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()));
+
+        // Still 2 calls left.
+        assertEquals(2, mManager.getRemainingCallCount());
+    }
+
+    public void testDeleteAllDynamicShortcuts() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+        final ShortcutInfo si2 = makeShortcut("shortcut2");
+        final ShortcutInfo si3 = makeShortcut("shortcut3");
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3)));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mManager.getDynamicShortcuts()),
+                "shortcut1", "shortcut2", "shortcut3");
+
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mManager.removeAllDynamicShortcuts();
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        // Note delete shouldn't affect throttling, so...
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+
+        // This should still work.
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3)));
+        assertEquals(3, mManager.getDynamicShortcuts().size());
+
+        // Still 1 call left
+        assertEquals(1, mManager.getRemainingCallCount());
+    }
+
+    public void testIcons() throws IOException {
+        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
+        final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
+        final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512);
+
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+        final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_64x64));
+        final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_512x512));
+
+        // Set from package 1
+        setCaller(CALLING_PACKAGE_1);
+        assertTrue(mManager.setDynamicShortcuts(list(
+                makeShortcutWithIcon("res32x32", res32x32),
+                makeShortcutWithIcon("res64x64", res64x64),
+                makeShortcutWithIcon("bmp32x32", bmp32x32),
+                makeShortcutWithIcon("bmp64x64", bmp64x64),
+                makeShortcutWithIcon("bmp512x512", bmp512x512),
+                makeShortcut("none")
+        )));
+
+        // getDynamicShortcuts() shouldn't return icons, thus assertAllNotHaveIcon().
+        assertShortcutIds(assertAllNotHaveIcon(mManager.getDynamicShortcuts()),
+                "res32x32",
+                "res64x64",
+                "bmp32x32",
+                "bmp64x64",
+                "bmp512x512",
+                "none");
+
+        // Call from another caller with the same ID, just to make sure storage is per-package.
+        setCaller(CALLING_PACKAGE_2);
+        assertTrue(mManager.setDynamicShortcuts(list(
+                makeShortcutWithIcon("res32x32", res512x512),
+                makeShortcutWithIcon("res64x64", res512x512),
+                makeShortcutWithIcon("none", res512x512)
+        )));
+        assertShortcutIds(assertAllNotHaveIcon(mManager.getDynamicShortcuts()),
+                "res32x32",
+                "res64x64",
+                "none");
+
+        // Different profile.  Note the names and the contents don't match.
+        setCaller(CALLING_PACKAGE_1, USER_P0);
+        assertTrue(mManager.setDynamicShortcuts(list(
+                makeShortcutWithIcon("res32x32", res512x512),
+                makeShortcutWithIcon("bmp32x32", bmp512x512)
+        )));
+        assertShortcutIds(assertAllNotHaveIcon(mManager.getDynamicShortcuts()),
+                "res32x32",
+                "bmp32x32");
+
+        // Re-initialize and load from the files.
+        mService.saveDirtyInfo();
+        initService();
+
+        // Load from launcher.
+        Bitmap bmp;
+
+        setCaller(LAUNCHER_1);
+        // Check hasIconResource()/hasIconFile().
+        assertShortcutIds(assertAllHaveIconResId(
+                list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_0))),
+                "res32x32");
+
+        assertShortcutIds(assertAllHaveIconResId(
+                list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res64x64", USER_0))),
+                "res64x64");
+
+        assertShortcutIds(assertAllHaveIconFile(
+                list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_0))),
+                "bmp32x32");
+
+        assertShortcutIds(assertAllHaveIconFile(
+                list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp64x64", USER_0))),
+                "bmp64x64");
+
+        assertShortcutIds(assertAllHaveIconFile(
+                list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp512x512", USER_0))),
+                "bmp512x512");
+
+        assertShortcutIds(assertAllHaveIconResId(
+                list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_P0))),
+                "res32x32");
+        assertShortcutIds(assertAllHaveIconFile(
+                list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_P0))),
+                "bmp32x32");
+
+        // Check
+        assertEquals(
+                R.drawable.black_32x32,
+                mLauncherApps.getShortcutIconResId(
+                        getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_0)));
+
+        assertEquals(
+                R.drawable.black_64x64,
+                mLauncherApps.getShortcutIconResId(
+                        getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res64x64", USER_0)));
+
+        assertEquals(
+                0, // because it's not a resource
+                mLauncherApps.getShortcutIconResId(
+                        getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_0)));
+        assertEquals(
+                0, // because it's not a resource
+                mLauncherApps.getShortcutIconResId(
+                        getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp64x64", USER_0)));
+        assertEquals(
+                0, // because it's not a resource
+                mLauncherApps.getShortcutIconResId(
+                        getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp512x512", USER_0)));
+
+        bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
+                getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_0)));
+        assertBitmapSize(32, 32, bmp);
+
+        bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
+                getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp64x64", USER_0)));
+        assertBitmapSize(64, 64, bmp);
+
+        bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
+                getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp512x512", USER_0)));
+        assertBitmapSize(128, 128, bmp);
+
+        assertEquals(
+                R.drawable.black_512x512,
+                mLauncherApps.getShortcutIconResId(
+                        getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_P0)));
+        // Should be 512x512, so shrunk.
+        bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
+                getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_P0)));
+        assertBitmapSize(128, 128, bmp);
+
+        // Also check the overload APIs too.
+        assertEquals(
+                R.drawable.black_32x32,
+                mLauncherApps.getShortcutIconResId(CALLING_PACKAGE_1, "res32x32", HANDLE_USER_0));
+        assertEquals(
+                R.drawable.black_64x64,
+                mLauncherApps.getShortcutIconResId(CALLING_PACKAGE_1, "res64x64", HANDLE_USER_0));
+        assertEquals(
+                R.drawable.black_512x512,
+                mLauncherApps.getShortcutIconResId(CALLING_PACKAGE_1, "res32x32", HANDLE_USER_P0));
+        bmp = pfdToBitmap(
+                mLauncherApps.getShortcutIconFd(CALLING_PACKAGE_1, "bmp32x32", HANDLE_USER_P0));
+        assertBitmapSize(128, 128, bmp);
+    }
+
+    public void testCleanupDanglingBitmaps() throws Exception {
+        assertBitmapDirectories(USER_0, EMPTY_STRINGS);
+        assertBitmapDirectories(USER_10, EMPTY_STRINGS);
+
+        // Make some shortcuts with bitmap icons.
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.setDynamicShortcuts(list(
+                    makeShortcutWithIcon("s1", bmp32x32),
+                    makeShortcutWithIcon("s2", bmp32x32),
+                    makeShortcutWithIcon("s3", bmp32x32)
+            ));
+        });
+
+        // Increment the time (which actually we don't have to), which is used for filenames.
+        mInjectedCurrentTimeMillis++;
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            mManager.setDynamicShortcuts(list(
+                    makeShortcutWithIcon("s4", bmp32x32),
+                    makeShortcutWithIcon("s5", bmp32x32),
+                    makeShortcutWithIcon("s6", bmp32x32)
+            ));
+        });
+
+        // Increment the time, which is used for filenames.
+        mInjectedCurrentTimeMillis++;
+
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            mManager.setDynamicShortcuts(list(
+            ));
+        });
+
+        // For USER-10, let's try without updating the times.
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            mManager.setDynamicShortcuts(list(
+                    makeShortcutWithIcon("10s1", bmp32x32),
+                    makeShortcutWithIcon("10s2", bmp32x32),
+                    makeShortcutWithIcon("10s3", bmp32x32)
+            ));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            mManager.setDynamicShortcuts(list(
+                    makeShortcutWithIcon("10s4", bmp32x32),
+                    makeShortcutWithIcon("10s5", bmp32x32),
+                    makeShortcutWithIcon("10s6", bmp32x32)
+            ));
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_10, () -> {
+            mManager.setDynamicShortcuts(list(
+            ));
+        });
+
+        dumpsysOnLogcat();
+
+        // Check files and directories.
+        // Package 3 has no bitmaps, so we don't create a directory.
+        assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
+        assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
+
+        assertBitmapFiles(USER_0, CALLING_PACKAGE_1,
+                getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s1"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s2"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s3")
+        );
+        assertBitmapFiles(USER_0, CALLING_PACKAGE_2,
+                getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s4"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s5"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s6")
+        );
+        assertBitmapFiles(USER_0, CALLING_PACKAGE_3,
+                EMPTY_STRINGS
+        );
+        assertBitmapFiles(USER_10, CALLING_PACKAGE_1,
+                getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s1"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s2"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s3")
+        );
+        assertBitmapFiles(USER_10, CALLING_PACKAGE_2,
+                getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s4"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s5"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s6")
+        );
+        assertBitmapFiles(USER_10, CALLING_PACKAGE_3,
+                EMPTY_STRINGS
+        );
+
+        // Then create random directories and files.
+        makeFile(mService.getUserBitmapFilePath(USER_0), "a.b.c").mkdir();
+        makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f").mkdir();
+        makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f", "123").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f", "456").createNewFile();
+
+        makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_3).mkdir();
+
+        makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "1").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "2").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "3").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "4").createNewFile();
+
+        makeFile(mService.getUserBitmapFilePath(USER_10), "10a.b.c").mkdir();
+        makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f").mkdir();
+        makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f", "123").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f", "456").createNewFile();
+
+        makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "1").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "2").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile();
+        makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile();
+
+        assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
+                "a.b.c", "d.e.f");
+
+        // Save and load.  When a user is loaded, we do the cleanup.
+        mService.saveDirtyInfo();
+        initService();
+
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_10);
+        mService.handleUnlockUser(20); // Make sure the logic will still work for nonexistent user.
+
+        // The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3
+        // directory.
+
+        assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3);
+        assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
+
+        assertBitmapFiles(USER_0, CALLING_PACKAGE_1,
+                getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s1"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s2"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s3")
+        );
+        assertBitmapFiles(USER_0, CALLING_PACKAGE_2,
+                getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s4"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s5"),
+                getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s6")
+        );
+        assertBitmapFiles(USER_0, CALLING_PACKAGE_3,
+                EMPTY_STRINGS
+        );
+        assertBitmapFiles(USER_10, CALLING_PACKAGE_1,
+                getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s1"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s2"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s3")
+        );
+        assertBitmapFiles(USER_10, CALLING_PACKAGE_2,
+                getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s4"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s5"),
+                getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s6")
+        );
+        assertBitmapFiles(USER_10, CALLING_PACKAGE_3,
+                EMPTY_STRINGS
+        );
+    }
+
+    protected void checkShrinkBitmap(int expectedWidth, int expectedHeight, int resId, int maxSize) {
+        assertBitmapSize(expectedWidth, expectedHeight,
+                ShortcutService.shrinkBitmap(BitmapFactory.decodeResource(
+                        getTestContext().getResources(), resId),
+                        maxSize));
+    }
+
+    public void testShrinkBitmap() {
+        checkShrinkBitmap(32, 32, R.drawable.black_512x512, 32);
+        checkShrinkBitmap(511, 511, R.drawable.black_512x512, 511);
+        checkShrinkBitmap(512, 512, R.drawable.black_512x512, 512);
+
+        checkShrinkBitmap(1024, 4096, R.drawable.black_1024x4096, 4096);
+        checkShrinkBitmap(1024, 4096, R.drawable.black_1024x4096, 4100);
+        checkShrinkBitmap(512, 2048, R.drawable.black_1024x4096, 2048);
+
+        checkShrinkBitmap(4096, 1024, R.drawable.black_4096x1024, 4096);
+        checkShrinkBitmap(4096, 1024, R.drawable.black_4096x1024, 4100);
+        checkShrinkBitmap(2048, 512, R.drawable.black_4096x1024, 2048);
+    }
+
+    protected File openIconFileForWriteAndGetPath(int userId, String packageName)
+            throws IOException {
+        // Shortcut IDs aren't used in the path, so just pass the same ID.
+        final FileOutputStreamWithPath out =
+                mService.openIconFileForWrite(userId, makePackageShortcut(packageName, "id"));
+        out.close();
+        return out.getFile();
+    }
+
+    public void testOpenIconFileForWrite() throws IOException {
+        mInjectedCurrentTimeMillis = 1000;
+
+        final File p10_1_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
+        final File p10_1_2 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
+
+        final File p10_2_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_2);
+        final File p10_2_2 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_2);
+
+        final File p11_1_1 = openIconFileForWriteAndGetPath(11, CALLING_PACKAGE_1);
+        final File p11_1_2 = openIconFileForWriteAndGetPath(11, CALLING_PACKAGE_1);
+
+        mInjectedCurrentTimeMillis++;
+
+        final File p10_1_3 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
+        final File p10_1_4 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
+        final File p10_1_5 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
+
+        final File p10_2_3 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_2);
+        final File p11_1_3 = openIconFileForWriteAndGetPath(11, CALLING_PACKAGE_1);
+
+        // Make sure their paths are all unique
+        assertAllUnique(list(
+                p10_1_1,
+                p10_1_2,
+                p10_1_3,
+                p10_1_4,
+                p10_1_5,
+
+                p10_2_1,
+                p10_2_2,
+                p10_2_3,
+
+                p11_1_1,
+                p11_1_2,
+                p11_1_3
+        ));
+
+        // Check each set has the same parent.
+        assertEquals(p10_1_1.getParent(), p10_1_2.getParent());
+        assertEquals(p10_1_1.getParent(), p10_1_3.getParent());
+        assertEquals(p10_1_1.getParent(), p10_1_4.getParent());
+        assertEquals(p10_1_1.getParent(), p10_1_5.getParent());
+
+        assertEquals(p10_2_1.getParent(), p10_2_2.getParent());
+        assertEquals(p10_2_1.getParent(), p10_2_3.getParent());
+
+        assertEquals(p11_1_1.getParent(), p11_1_2.getParent());
+        assertEquals(p11_1_1.getParent(), p11_1_3.getParent());
+
+        // Check the parents are still unique.
+        assertAllUnique(list(
+                p10_1_1.getParent(),
+                p10_2_1.getParent(),
+                p11_1_1.getParent()
+        ));
+
+        // All files created at the same time for the same package/user, expcet for the first ones,
+        // will have "_" in the path.
+        assertFalse(p10_1_1.getName().contains("_"));
+        assertTrue(p10_1_2.getName().contains("_"));
+        assertFalse(p10_1_3.getName().contains("_"));
+        assertTrue(p10_1_4.getName().contains("_"));
+        assertTrue(p10_1_5.getName().contains("_"));
+
+        assertFalse(p10_2_1.getName().contains("_"));
+        assertTrue(p10_2_2.getName().contains("_"));
+        assertFalse(p10_2_3.getName().contains("_"));
+
+        assertFalse(p11_1_1.getName().contains("_"));
+        assertTrue(p11_1_2.getName().contains("_"));
+        assertFalse(p11_1_3.getName().contains("_"));
+    }
+
+    public void testUpdateShortcuts() {
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+        });
+        runWithCaller(LAUNCHER_1, UserHandle.USER_SYSTEM, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s3"),
+                    getCallingUser());
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s4", "s5"),
+                    getCallingUser());
+        });
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            mManager.removeDynamicShortcuts(list("s1"));
+            mManager.removeDynamicShortcuts(list("s2"));
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            mManager.removeDynamicShortcuts(list("s1"));
+            mManager.removeDynamicShortcuts(list("s3"));
+            mManager.removeDynamicShortcuts(list("s5"));
+        });
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(
+                    mManager.getDynamicShortcuts()),
+                    "s3", "s4", "s5");
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s2", "s3");
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(
+                    mManager.getDynamicShortcuts()),
+                    "s2", "s4");
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s4", "s5");
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            ShortcutInfo s2 = makeShortcutBuilder()
+                    .setId("s2")
+                    .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
+                    .build();
+
+            ShortcutInfo s4 = makeShortcutBuilder()
+                    .setId("s4")
+                    .setTitle("new title")
+                    .build();
+
+            mManager.updateShortcuts(list(s2, s4));
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            ShortcutInfo s2 = makeShortcutBuilder()
+                    .setId("s2")
+                    .setIntent(makeIntent(Intent.ACTION_ANSWER, ShortcutActivity.class,
+                            "key1", "val1"))
+                    .build();
+
+            ShortcutInfo s4 = makeShortcutBuilder()
+                    .setId("s4")
+                    .setIntent(new Intent(Intent.ACTION_ALL_APPS))
+                    .build();
+
+            mManager.updateShortcuts(list(s2, s4));
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(
+                    mManager.getDynamicShortcuts()),
+                    "s3", "s4", "s5");
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s2", "s3");
+
+            ShortcutInfo s = getCallerShortcut("s2");
+            assertTrue(s.hasIconResource());
+            assertEquals(R.drawable.black_32x32, s.getIconResourceId());
+            assertEquals("string/r" + R.drawable.black_32x32, s.getIconResName());
+            assertEquals("Title-s2", s.getTitle());
+
+            s = getCallerShortcut("s4");
+            assertFalse(s.hasIconResource());
+            assertEquals(0, s.getIconResourceId());
+            assertEquals("new title", s.getTitle());
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(
+                    mManager.getDynamicShortcuts()),
+                    "s2", "s4");
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s4", "s5");
+
+            ShortcutInfo s = getCallerShortcut("s2");
+            assertFalse(s.hasIconResource());
+            assertEquals(0, s.getIconResourceId());
+            assertEquals("Title-s2", s.getTitle());
+            assertEquals(Intent.ACTION_ANSWER, s.getIntent().getAction());
+            assertEquals(1, s.getIntent().getExtras().size());
+
+            s = getCallerShortcut("s4");
+            assertFalse(s.hasIconResource());
+            assertEquals(0, s.getIconResourceId());
+            assertEquals("Title-s4", s.getTitle());
+            assertEquals(Intent.ACTION_ALL_APPS, s.getIntent().getAction());
+            assertBundleEmpty(s.getIntent().getExtras());
+        });
+        // TODO Check with other fields too.
+
+        // TODO Check bitmap removal too.
+
+        runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
+            mManager.updateShortcuts(list());
+        });
+    }
+
+    // === Test for launcher side APIs ===
+
+    public void testGetShortcuts() {
+
+        // Set up shortcuts.
+
+        setCaller(CALLING_PACKAGE_1);
+        final ShortcutInfo s1_1 = makeShortcut("s1");
+        final ShortcutInfo s1_2 = makeShortcut("s2");
+
+        assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
+
+        // Because setDynamicShortcuts will update the timestamps when ranks are changing,
+        // we explicitly set timestamps here.
+        getCallerShortcut("s1").setTimestamp(5000);
+        getCallerShortcut("s2").setTimestamp(1000);
+
+        setCaller(CALLING_PACKAGE_2);
+        final ShortcutInfo s2_2 = makeShortcut("s2");
+        final ShortcutInfo s2_3 = makeShortcutWithActivity("s3",
+                makeComponent(ShortcutActivity2.class));
+        final ShortcutInfo s2_4 = makeShortcutWithActivity("s4",
+                makeComponent(ShortcutActivity.class));
+        assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
+
+        getCallerShortcut("s2").setTimestamp(1500);
+        getCallerShortcut("s3").setTimestamp(3000);
+        getCallerShortcut("s4").setTimestamp(500);
+
+        setCaller(CALLING_PACKAGE_3);
+        final ShortcutInfo s3_2 = makeShortcut("s3");
+        assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
+
+        getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
+
+        setCaller(LAUNCHER_1);
+
+        // Get dynamic
+        assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertAllStringsResolved(
+                assertShortcutIds(
+                        assertAllNotKeyFieldsOnly(
+                                mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                        /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                        "s1", "s2")))));
+
+        // Get pinned
+        assertShortcutIds(
+                mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_PINNED), getCallingUser())
+                /* none */);
+
+        // Get both, with timestamp
+        assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllNotKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_2,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC),
+                        getCallingUser())),
+                "s2", "s3"))));
+
+        // FLAG_GET_KEY_FIELDS_ONLY
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_2,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser())),
+                "s2", "s3"))));
+
+        // Filter by activity
+        assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllNotKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 0, CALLING_PACKAGE_2,
+                        new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                        ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC),
+                        getCallingUser())),
+                "s4"))));
+
+        // With ID.
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"),
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser())),
+                "s3"))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list("s3", "s2", "ss"),
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser())),
+                "s2", "s3"))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list("s3x", "s2x"),
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser()))
+                /* empty */))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list(),
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser()))
+                /* empty */))));
+
+        // Pin some shortcuts.
+        mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                list("s3", "s4"), getCallingUser());
+
+        // Pinned ones only
+        assertAllPinned(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllNotKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_2,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_PINNED),
+                        getCallingUser())),
+                "s3"))));
+
+        // All packages.
+        assertShortcutIds(assertAllNotKeyFieldsOnly(
+                mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 5000, /* package= */ null,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED),
+                        getCallingUser())),
+                "s1", "s3");
+
+        assertExpectException(
+                IllegalArgumentException.class, "package name must also be set", () -> {
+                    mLauncherApps.getShortcuts(buildQuery(
+                    /* time =*/ 0, /* package= */ null, list("id"),
+                    /* activity =*/ null, /* flags */ 0), getCallingUser());
+                });
+
+        // TODO More tests: pinned but dynamic.
+    }
+
+    public void testGetShortcuts_resolveStrings() throws Exception {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
+                    .setId("id")
+                    .setActivity(new ComponentName(mClientContext, "dummy"))
+                    .setTitleResId(10)
+                    .setTextResId(11)
+                    .setDisabledMessageResId(12)
+                    .setIntent(makeIntent("action", ShortcutActivity.class))
+                    .build();
+            mManager.setDynamicShortcuts(list(si));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
+                    .setId("id")
+                    .setActivity(new ComponentName(mClientContext, "dummy"))
+                    .setTitleResId(10)
+                    .setTextResId(11)
+                    .setDisabledMessageResId(12)
+                    .setIntent(makeIntent("action", ShortcutActivity.class))
+                    .build();
+            mManager.setDynamicShortcuts(list(si));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            final ShortcutQuery q = new ShortcutQuery();
+            q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC);
+
+            // USER 0
+            List<ShortcutInfo> ret = assertShortcutIds(
+                    assertAllStringsResolved(mLauncherApps.getShortcuts(q, HANDLE_USER_0)),
+                    "id");
+            assertEquals("string-com.android.test.1-user:0-res:10/en", ret.get(0).getTitle());
+            assertEquals("string-com.android.test.1-user:0-res:11/en", ret.get(0).getText());
+            assertEquals("string-com.android.test.1-user:0-res:12/en",
+                    ret.get(0).getDisabledMessage());
+
+            // USER P0
+            ret = assertShortcutIds(
+                    assertAllStringsResolved(mLauncherApps.getShortcuts(q, HANDLE_USER_P0)),
+                    "id");
+            assertEquals("string-com.android.test.1-user:20-res:10/en", ret.get(0).getTitle());
+            assertEquals("string-com.android.test.1-user:20-res:11/en", ret.get(0).getText());
+            assertEquals("string-com.android.test.1-user:20-res:12/en",
+                    ret.get(0).getDisabledMessage());
+        });
+    }
+
+    // TODO resource
+    public void testGetShortcutInfo() {
+        // Create shortcuts.
+        setCaller(CALLING_PACKAGE_1);
+        final ShortcutInfo s1_1 = makeShortcut(
+                "s1",
+                "Title 1",
+                makeComponent(ShortcutActivity.class),
+                /* icon =*/ null,
+                makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                        "key1", "val1", "nest", makeBundle("key", 123)),
+                /* weight */ 10);
+
+        final ShortcutInfo s1_2 = makeShortcut(
+                "s2",
+                "Title 2",
+                /* activity */ null,
+                /* icon =*/ null,
+                makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                /* weight */ 12);
+
+        assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
+        dumpsysOnLogcat();
+
+        setCaller(CALLING_PACKAGE_2);
+        final ShortcutInfo s2_1 = makeShortcut(
+                "s1",
+                "ABC",
+                makeComponent(ShortcutActivity2.class),
+                /* icon =*/ null,
+                makeIntent(Intent.ACTION_ANSWER, ShortcutActivity2.class,
+                        "key1", "val1", "nest", makeBundle("key", 123)),
+                /* weight */ 10);
+        assertTrue(mManager.setDynamicShortcuts(list(s2_1)));
+        dumpsysOnLogcat();
+
+        // Pin some.
+        setCaller(LAUNCHER_1);
+
+        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                list("s2"), getCallingUser());
+
+        dumpsysOnLogcat();
+
+        // Delete some.
+        setCaller(CALLING_PACKAGE_1);
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+        mManager.removeDynamicShortcuts(list("s2"));
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+
+        dumpsysOnLogcat();
+
+        setCaller(LAUNCHER_1);
+        List<ShortcutInfo> list;
+
+        // Note we don't guarantee the orders.
+        list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
+                assertAllNotKeyFieldsOnly(
+                        mLauncherApps.getShortcutInfo(CALLING_PACKAGE_1,
+                                list("s2", "s1", "s3", null), getCallingUser())))),
+                "s1", "s2");
+        assertEquals("Title 1", findById(list, "s1").getTitle());
+        assertEquals("Title 2", findById(list, "s2").getTitle());
+
+        assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
+                mLauncherApps.getShortcutInfo(CALLING_PACKAGE_1,
+                        list("s3"), getCallingUser())))
+                /* none */);
+
+        list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
+                mLauncherApps.getShortcutInfo(CALLING_PACKAGE_2,
+                        list("s1", "s2", "s3"), getCallingUser()))),
+                "s1");
+        assertEquals("ABC", findById(list, "s1").getTitle());
+    }
+
+    public void testPinShortcutAndGetPinnedShortcuts() {
+        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
+        });
+
+        // Delete some.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            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(assertAllEnabled(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* 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(assertAllEnabled(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_3,
+                    /* 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() {
+        // Create some shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        dumpsysOnLogcat();
+
+        // Pin some.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3", "s4"), getCallingUser());
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s1", "s2", "s4"), getCallingUser());
+        });
+
+        dumpsysOnLogcat();
+
+        // Delete some.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s3");
+            mManager.removeDynamicShortcuts(list("s3"));
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s3");
+        });
+
+        dumpsysOnLogcat();
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s1", "s2");
+            mManager.removeDynamicShortcuts(list("s1"));
+            mManager.removeDynamicShortcuts(list("s3"));
+            assertShortcutIds(mManager.getPinnedShortcuts(), "s1", "s2");
+        });
+
+        dumpsysOnLogcat();
+
+        // Get pinned shortcuts from launcher
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s3");
+
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s1", "s2");
+
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s1", "s2", "s3");
+
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s1", "s2");
+        });
+
+        dumpsysOnLogcat();
+
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            // Launcher2 still has no pinned ones.
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
+                    /* none */);
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
+                    /* none */);
+
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s1", "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s2");
+
+            // Now pin some.
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1", "s2"), getCallingUser());
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s1", "s2"), getCallingUser());
+
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s1", "s2");
+
+            // S1 was not visible to it, so shouldn't be pinned.
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s2");
+        });
+
+        // Re-initialize and load from the files.
+        mService.saveDirtyInfo();
+        initService();
+
+        // Load from file.
+        mService.handleUnlockUser(USER_0);
+
+        // Make sure package info is restored too.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s3");
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s1", "s2");
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s1", "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
+                                    | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
+                    "s2");
+        });
+
+        // Delete all dynamic.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.removeAllDynamicShortcuts();
+
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2", "s3");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            mManager.removeAllDynamicShortcuts();
+
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s2", "s1");
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s3");
+
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s1", "s2");
+
+            // from all packages.
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, null,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s1", "s2", "s3");
+
+            // Update pined.  Note s2 and s3 are actually available, but not visible to this
+            // launcher, so still can't be pinned.
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
+                    getCallingUser());
+
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s3");
+        });
+        // Re-publish s1.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1"))));
+
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), "s1");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2", "s3");
+        });
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s3");
+
+            // Now "s1" is visible, so can be pinned.
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
+                    getCallingUser());
+
+            assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+                    "s1", "s3");
+        });
+
+        // Now clear pinned shortcuts.  First, from launcher 1.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), getCallingUser());
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), getCallingUser());
+
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), "s1");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s2");
+        });
+
+        // Clear all pins from launcher 2.
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), getCallingUser());
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), getCallingUser());
+
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), "s1");
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+    }
+
+    public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
+        // Create some shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+
+        // Pin some shortcuts and see the result.
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s1", "s2", "s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s2", "s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_2, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1", "s2", "s3"), HANDLE_USER_10);
+        });
+
+        // Cross profile pinning.
+        final int PIN_AND_DYNAMIC = ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC;
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_10, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_10)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_10)),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_10)),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+        });
+
+        // Remove some dynamic shortcuts.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_10, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_10)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_10)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_10)),
+                    "s1", "s2", "s3");
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+
+        // Save & load and make sure we still have the same information.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+    }
+
+    public void testStartShortcut() {
+        // Create some shortcuts.
+        setCaller(CALLING_PACKAGE_1);
+        final ShortcutInfo s1_1 = makeShortcut(
+                "s1",
+                "Title 1",
+                makeComponent(ShortcutActivity.class),
+                /* icon =*/ null,
+                makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                        "key1", "val1", "nest", makeBundle("key", 123)),
+                /* weight */ 10);
+
+        final ShortcutInfo s1_2 = makeShortcut(
+                "s2",
+                "Title 2",
+                /* activity */ null,
+                /* icon =*/ null,
+                makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                /* weight */ 12);
+
+        assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
+
+        setCaller(CALLING_PACKAGE_2);
+        final ShortcutInfo s2_1 = makeShortcut(
+                "s1",
+                "ABC",
+                makeComponent(ShortcutActivity.class),
+                /* icon =*/ null,
+                makeIntent(Intent.ACTION_ANSWER, ShortcutActivity.class,
+                        "key1", "val1", "nest", makeBundle("key", 123)),
+                /* weight */ 10);
+        assertTrue(mManager.setDynamicShortcuts(list(s2_1)));
+
+        // Pin all.
+        setCaller(LAUNCHER_1);
+        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                list("s1", "s2"), getCallingUser());
+
+        mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                list("s1"), getCallingUser());
+
+        // Just to make it complicated, delete some.
+        setCaller(CALLING_PACKAGE_1);
+        mManager.removeDynamicShortcuts(list("s2"));
+
+        // intent and check.
+        setCaller(LAUNCHER_1);
+
+        Intent intent;
+        intent = launchShortcutAndGetIntent(CALLING_PACKAGE_1, "s1", USER_0);
+        assertEquals(ShortcutActivity2.class.getName(), intent.getComponent().getClassName());
+
+
+        intent = launchShortcutAndGetIntent(CALLING_PACKAGE_1, "s2", USER_0);
+        assertEquals(ShortcutActivity3.class.getName(), intent.getComponent().getClassName());
+
+        intent = launchShortcutAndGetIntent(CALLING_PACKAGE_2, "s1", USER_0);
+        assertEquals(ShortcutActivity.class.getName(), intent.getComponent().getClassName());
+
+        // TODO Check extra, etc
+    }
+
+    public void testLauncherCallback() throws Throwable {
+        LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
+
+        // Set listeners
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        waitOnMainThread();
+        ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
+                "s1", "s2", "s3");
+
+        // From different package.
+        reset(c0);
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_2),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
+                "s1", "s2", "s3");
+
+        // Different user, callback shouldn't be called.
+        reset(c0);
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        waitOnMainThread();
+        verify(c0, times(0)).onShortcutsChanged(
+                anyString(),
+                any(List.class),
+                any(UserHandle.class)
+        );
+
+        // Test for addDynamicShortcuts.
+        reset(c0);
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            dumpsysOnLogcat("before addDynamicShortcuts");
+            assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
+        });
+
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
+                "s1", "s2", "s3", "s4");
+
+        // Test for remove
+        reset(c0);
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            mManager.removeDynamicShortcuts(list("s1"));
+        });
+
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
+                "s2", "s3", "s4");
+
+        // Test for update
+        reset(c0);
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertTrue(mManager.updateShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"))));
+        });
+
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
+                "s2", "s3", "s4");
+
+        // Test for deleteAll
+        reset(c0);
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            mManager.removeAllDynamicShortcuts();
+        });
+
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertEquals(0, shortcuts.getValue().size());
+
+        // Remove CALLING_PACKAGE_2
+        reset(c0);
+        uninstallPackage(USER_0, CALLING_PACKAGE_2);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
+
+        // Should get a callback with an empty list.
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_2),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertEquals(0, shortcuts.getValue().size());
+    }
+
+    public void testLauncherCallback_crossProfile() throws Throwable {
+        prepareCrossProfileDataSet();
+
+        final Handler h = new Handler(Looper.getMainLooper());
+
+        final LauncherApps.Callback c0_1 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c0_2 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c0_3 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c0_4 = mock(LauncherApps.Callback.class);
+
+        final LauncherApps.Callback cP0_1 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c10_1 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c10_2 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c11_1 = mock(LauncherApps.Callback.class);
+
+        final List<LauncherApps.Callback> all =
+                list(c0_1, c0_2, c0_3, c0_4, cP0_1, c10_1, c11_1);
+
+        setDefaultLauncherChecker((pkg, userId) -> {
+            switch (userId) {
+                case USER_0:
+                    return LAUNCHER_2.equals(pkg);
+                case USER_P0:
+                    return LAUNCHER_1.equals(pkg);
+                case USER_10:
+                    return LAUNCHER_1.equals(pkg);
+                case USER_11:
+                    return LAUNCHER_1.equals(pkg);
+                default:
+                    return false;
+            }
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> mLauncherApps.registerCallback(c0_1, h));
+        runWithCaller(LAUNCHER_2, USER_0, () -> mLauncherApps.registerCallback(c0_2, h));
+        runWithCaller(LAUNCHER_3, USER_0, () -> mLauncherApps.registerCallback(c0_3, h));
+        runWithCaller(LAUNCHER_4, USER_0, () -> mLauncherApps.registerCallback(c0_4, h));
+        runWithCaller(LAUNCHER_1, USER_P0, () -> mLauncherApps.registerCallback(cP0_1, h));
+        runWithCaller(LAUNCHER_1, USER_10, () -> mLauncherApps.registerCallback(c10_1, h));
+        runWithCaller(LAUNCHER_2, USER_10, () -> mLauncherApps.registerCallback(c10_2, h));
+        runWithCaller(LAUNCHER_1, USER_11, () -> mLauncherApps.registerCallback(c11_1, h));
+
+        // User 0.
+
+        resetAll(all);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.removeDynamicShortcuts(list());
+        });
+        waitOnMainThread();
+
+        assertCallbackNotReceived(c0_1);
+        assertCallbackNotReceived(c0_3);
+        assertCallbackNotReceived(c0_4);
+        assertCallbackNotReceived(c10_1);
+        assertCallbackNotReceived(c10_2);
+        assertCallbackNotReceived(c11_1);
+        assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3");
+        assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
+
+        // User 0, different package.
+
+        resetAll(all);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            mManager.removeDynamicShortcuts(list());
+        });
+        waitOnMainThread();
+
+        assertCallbackNotReceived(c0_1);
+        assertCallbackNotReceived(c0_3);
+        assertCallbackNotReceived(c0_4);
+        assertCallbackNotReceived(c10_1);
+        assertCallbackNotReceived(c10_2);
+        assertCallbackNotReceived(c11_1);
+        assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_3, "s1", "s2", "s3", "s4");
+        assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_3,
+                "s1", "s2", "s3", "s4", "s5", "s6");
+
+        // Work profile, but not running, so don't send notifications.
+
+        resetAll(all);
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            mManager.removeDynamicShortcuts(list());
+        });
+        waitOnMainThread();
+
+        assertCallbackNotReceived(c0_1);
+        assertCallbackNotReceived(c0_2);
+        assertCallbackNotReceived(c0_3);
+        assertCallbackNotReceived(c0_4);
+        assertCallbackNotReceived(cP0_1);
+        assertCallbackNotReceived(c10_1);
+        assertCallbackNotReceived(c10_2);
+        assertCallbackNotReceived(c11_1);
+
+        // Work profile, now running.
+        doAnswer(new AnswerIsUserRunning(false)).when(mMockUserManager).isUserRunning(anyInt());
+        doAnswer(new AnswerIsUserRunning(true)).when(mMockUserManager).isUserRunning(eq(USER_P0));
+
+        resetAll(all);
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            mManager.removeDynamicShortcuts(list());
+        });
+        waitOnMainThread();
+
+        assertCallbackNotReceived(c0_1);
+        assertCallbackNotReceived(c0_3);
+        assertCallbackNotReceived(c0_4);
+        assertCallbackNotReceived(c10_1);
+        assertCallbackNotReceived(c10_2);
+        assertCallbackNotReceived(c11_1);
+        assertCallbackReceived(c0_2, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s5");
+        assertCallbackReceived(cP0_1, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
+
+        // Normal secondary user.
+
+        doAnswer(new AnswerIsUserRunning(false)).when(mMockUserManager).isUserRunning(anyInt());
+        doAnswer(new AnswerIsUserRunning(true)).when(mMockUserManager).isUserRunning(eq(USER_10));
+
+        resetAll(all);
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            mManager.removeDynamicShortcuts(list());
+        });
+        waitOnMainThread();
+
+        assertCallbackNotReceived(c0_1);
+        assertCallbackNotReceived(c0_2);
+        assertCallbackNotReceived(c0_3);
+        assertCallbackNotReceived(c0_4);
+        assertCallbackNotReceived(cP0_1);
+        assertCallbackNotReceived(c10_2);
+        assertCallbackNotReceived(c11_1);
+        assertCallbackReceived(c10_1, HANDLE_USER_10, CALLING_PACKAGE_1,
+                "x1", "x2", "x3", "x4", "x5");
+    }
+
+    // === Test for persisting ===
+
+    public void testSaveAndLoadUser_empty() {
+        assertTrue(mManager.setDynamicShortcuts(list()));
+
+        Log.i(TAG, "Saved state");
+        dumpsysOnLogcat();
+        dumpUserFile(0);
+
+        // Restore.
+        mService.saveDirtyInfo();
+        initService();
+
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+    }
+
+    /**
+     * Try save and load, also stop/start the user.
+     */
+    public void testSaveAndLoadUser() {
+        // First, create some shortcuts and save.
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
+            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                    getTestContext().getResources(), R.drawable.icon2));
+
+            final ShortcutInfo si1 = makeShortcut(
+                    "s1",
+                    "title1-1",
+                    makeComponent(ShortcutActivity.class),
+                    icon1,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                            "key1", "val1", "nest", makeBundle("key", 123)),
+                        /* weight */ 10);
+
+            final ShortcutInfo si2 = makeShortcut(
+                    "s2",
+                    "title1-2",
+                        /* activity */ null,
+                    icon2,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                        /* weight */ 12);
+
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+            assertEquals(2, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_16x64);
+            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                    getTestContext().getResources(), R.drawable.icon2));
+
+            final ShortcutInfo si1 = makeShortcut(
+                    "s1",
+                    "title2-1",
+                    makeComponent(ShortcutActivity.class),
+                    icon1,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                            "key1", "val1", "nest", makeBundle("key", 123)),
+                        /* weight */ 10);
+
+            final ShortcutInfo si2 = makeShortcut(
+                    "s2",
+                    "title2-2",
+                        /* activity */ null,
+                    icon2,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                        /* weight */ 12);
+
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+            assertEquals(2, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
+            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                    getTestContext().getResources(), R.drawable.icon2));
+
+            final ShortcutInfo si1 = makeShortcut(
+                    "s1",
+                    "title10-1-1",
+                    makeComponent(ShortcutActivity.class),
+                    icon1,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                            "key1", "val1", "nest", makeBundle("key", 123)),
+                        /* weight */ 10);
+
+            final ShortcutInfo si2 = makeShortcut(
+                    "s2",
+                    "title10-1-2",
+                        /* activity */ null,
+                    icon2,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                        /* weight */ 12);
+
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+            assertEquals(2, mManager.getRemainingCallCount());
+        });
+
+        mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setDefaultLauncherComponent(
+                new ComponentName("pkg1", "class"));
+
+        // Restore.
+        mService.saveDirtyInfo();
+        initService();
+
+        // Before the load, the map should be empty.
+        assertEquals(0, mService.getShortcutsForTest().size());
+
+        // this will pre-load the per-user info.
+        mService.handleUnlockUser(UserHandle.USER_SYSTEM);
+
+        // Now it's loaded.
+        assertEquals(1, mService.getShortcutsForTest().size());
+
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+                    mManager.getDynamicShortcuts()))), "s1", "s2");
+            assertEquals(2, mManager.getRemainingCallCount());
+
+            assertEquals("title1-1", getCallerShortcut("s1").getTitle());
+            assertEquals("title1-2", getCallerShortcut("s2").getTitle());
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+                    mManager.getDynamicShortcuts()))), "s1", "s2");
+            assertEquals(2, mManager.getRemainingCallCount());
+
+            assertEquals("title2-1", getCallerShortcut("s1").getTitle());
+            assertEquals("title2-2", getCallerShortcut("s2").getTitle());
+        });
+
+        assertEquals("pkg1", mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM)
+                .getDefaultLauncherComponent().getPackageName());
+
+        // Start another user
+        mService.handleUnlockUser(USER_10);
+
+        // Now the size is 2.
+        assertEquals(2, mService.getShortcutsForTest().size());
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+                    mManager.getDynamicShortcuts()))), "s1", "s2");
+            assertEquals(2, mManager.getRemainingCallCount());
+
+            assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
+            assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
+        });
+        assertNull(mService.getShortcutsForTest().get(USER_10).getDefaultLauncherComponent());
+
+        // Try stopping the user
+        mService.handleCleanupUser(USER_10);
+
+        // Now it's unloaded.
+        assertEquals(1, mService.getShortcutsForTest().size());
+
+        // TODO Check all other fields
+    }
+
+    public void testCleanupPackage() {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s0_1"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s0_2"))));
+        });
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s0_1"),
+                    HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s0_2"),
+                    HANDLE_USER_0);
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s0_1"),
+                    HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s0_2"),
+                    HANDLE_USER_0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s10_1"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s10_2"))));
+        });
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s10_1"),
+                    HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s10_2"),
+                    HANDLE_USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s10_1"),
+                    HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s10_2"),
+                    HANDLE_USER_10);
+        });
+
+        // Remove all dynamic shortcuts; now all shortcuts are just pinned.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.removeAllDynamicShortcuts();
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            mManager.removeAllDynamicShortcuts();
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            mManager.removeAllDynamicShortcuts();
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            mManager.removeAllDynamicShortcuts();
+        });
+
+
+        final SparseArray<ShortcutUser> users =  mService.getShortcutsForTest();
+        assertEquals(2, users.size());
+        assertEquals(USER_0, users.keyAt(0));
+        assertEquals(USER_10, users.keyAt(1));
+
+        final ShortcutUser user0 =  users.get(USER_0);
+        final ShortcutUser user10 =  users.get(USER_10);
+
+
+        // Check the registered packages.
+        dumpsysOnLogcat();
+        assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+                hashSet(user0.getAllPackagesForTest().keySet()));
+        assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+                hashSet(user10.getAllPackagesForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                hashSet(user0.getAllLaunchersForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_10, LAUNCHER_1),
+                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                hashSet(user10.getAllLaunchersForTest().keySet()));
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+                "s0_1", "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+                "s0_1", "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
+                "s10_1", "s10_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+                "s10_1", "s10_2");
+        assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+        assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+        mService.saveDirtyInfo();
+
+        // Nonexistent package.
+        uninstallPackage(USER_0, "abc");
+        mService.cleanUpPackageLocked("abc", USER_0, USER_0);
+
+        // No changes.
+        assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+                hashSet(user0.getAllPackagesForTest().keySet()));
+        assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+                hashSet(user10.getAllPackagesForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                hashSet(user0.getAllLaunchersForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_10, LAUNCHER_1),
+                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                hashSet(user10.getAllLaunchersForTest().keySet()));
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+                "s0_1", "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+                "s0_1", "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
+                "s10_1", "s10_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+                "s10_1", "s10_2");
+        assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+        assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+        mService.saveDirtyInfo();
+
+        // Remove a package.
+        uninstallPackage(USER_0, CALLING_PACKAGE_1);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
+
+        assertEquals(set(CALLING_PACKAGE_2),
+                hashSet(user0.getAllPackagesForTest().keySet()));
+        assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+                hashSet(user10.getAllPackagesForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                hashSet(user0.getAllLaunchersForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_10, LAUNCHER_1),
+                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                hashSet(user10.getAllLaunchersForTest().keySet()));
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
+                "s10_1", "s10_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+                "s10_1", "s10_2");
+        assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+        assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+        mService.saveDirtyInfo();
+
+        // Remove a launcher.
+        uninstallPackage(USER_10, LAUNCHER_1);
+        mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
+
+        assertEquals(set(CALLING_PACKAGE_2),
+                hashSet(user0.getAllPackagesForTest().keySet()));
+        assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
+                hashSet(user10.getAllPackagesForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                hashSet(user0.getAllLaunchersForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                hashSet(user10.getAllLaunchersForTest().keySet()));
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+                "s10_1", "s10_2");
+        assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+        assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+        mService.saveDirtyInfo();
+
+        // Remove a package.
+        uninstallPackage(USER_10, CALLING_PACKAGE_2);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
+
+        assertEquals(set(CALLING_PACKAGE_2),
+                hashSet(user0.getAllPackagesForTest().keySet()));
+        assertEquals(set(CALLING_PACKAGE_1),
+                hashSet(user10.getAllPackagesForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                hashSet(user0.getAllLaunchersForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                hashSet(user10.getAllLaunchersForTest().keySet()));
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
+                "s10_1");
+        assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+        assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+        mService.saveDirtyInfo();
+
+        // Remove the other launcher from user 10 too.
+        uninstallPackage(USER_10, LAUNCHER_2);
+        mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
+
+        assertEquals(set(CALLING_PACKAGE_2),
+                hashSet(user0.getAllPackagesForTest().keySet()));
+        assertEquals(set(CALLING_PACKAGE_1),
+                hashSet(user10.getAllPackagesForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                hashSet(user0.getAllLaunchersForTest().keySet()));
+        assertEquals(
+                set(),
+                hashSet(user10.getAllLaunchersForTest().keySet()));
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+                "s0_2");
+
+        // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed.
+        assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+        assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+        assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+        mService.saveDirtyInfo();
+
+        // More remove.
+        uninstallPackage(USER_10, CALLING_PACKAGE_1);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
+
+        assertEquals(set(CALLING_PACKAGE_2),
+                hashSet(user0.getAllPackagesForTest().keySet()));
+        assertEquals(set(),
+                hashSet(user10.getAllPackagesForTest().keySet()));
+        assertEquals(
+                set(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                hashSet(user0.getAllLaunchersForTest().keySet()));
+        assertEquals(set(),
+                hashSet(user10.getAllLaunchersForTest().keySet()));
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
+                "s0_2");
+        assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
+                "s0_2");
+
+        // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed.
+        assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
+        assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
+        assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10);
+        assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
+
+        mService.saveDirtyInfo();
+    }
+
+    public void testHandleGonePackage_crossProfile() {
+        // Create some shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Pin some.
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), UserHandle.of(USER_P0));
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), UserHandle.of(USER_P0));
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s1"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), HANDLE_USER_10);
+        });
+
+        // Check the state.
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Make sure all the information is persisted.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Start uninstalling.
+        uninstallPackage(USER_10, LAUNCHER_1);
+        mService.checkPackageChanges(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Uninstall.
+        uninstallPackage(USER_10, CALLING_PACKAGE_1);
+        mService.checkPackageChanges(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        uninstallPackage(USER_P0, LAUNCHER_1);
+        mService.checkPackageChanges(USER_0);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        mService.checkPackageChanges(USER_P0);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        uninstallPackage(USER_P0, CALLING_PACKAGE_1);
+
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Uninstall
+        uninstallPackage(USER_0, LAUNCHER_1);
+
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        uninstallPackage(USER_0, CALLING_PACKAGE_2);
+
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+    }
+
+    protected void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi,
+            int version, String... signatures) {
+        assertEquals(expected, spi.canRestoreTo(mService, genPackage(
+                "dummy", /* uid */ 0, version, signatures)));
+    }
+
+    public void testCanRestoreTo() {
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
+        addPackage(CALLING_PACKAGE_2, CALLING_UID_1, 10, "sig1", "sig2");
+
+        final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage(
+                mService, CALLING_PACKAGE_1, USER_0);
+        final ShortcutPackageInfo spi2 = ShortcutPackageInfo.generateForInstalledPackage(
+                mService, CALLING_PACKAGE_2, USER_0);
+
+        checkCanRestoreTo(true, spi1, 10, "sig1");
+        checkCanRestoreTo(true, spi1, 10, "x", "sig1");
+        checkCanRestoreTo(true, spi1, 10, "sig1", "y");
+        checkCanRestoreTo(true, spi1, 10, "x", "sig1", "y");
+        checkCanRestoreTo(true, spi1, 11, "sig1");
+
+        checkCanRestoreTo(false, spi1, 10 /* empty */);
+        checkCanRestoreTo(false, spi1, 10, "x");
+        checkCanRestoreTo(false, spi1, 10, "x", "y");
+        checkCanRestoreTo(false, spi1, 10, "x");
+        checkCanRestoreTo(false, spi1, 9, "sig1");
+
+        checkCanRestoreTo(true, spi2, 10, "sig1", "sig2");
+        checkCanRestoreTo(true, spi2, 10, "sig2", "sig1");
+        checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2");
+        checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1");
+        checkCanRestoreTo(true, spi2, 10, "sig1", "sig2", "y");
+        checkCanRestoreTo(true, spi2, 10, "sig2", "sig1", "y");
+        checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2", "y");
+        checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1", "y");
+        checkCanRestoreTo(true, spi2, 11, "x", "sig2", "sig1", "y");
+
+        checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x");
+        checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x");
+        checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2");
+        checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1");
+        checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x", "y");
+        checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x", "y");
+        checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2", "y");
+        checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1", "y");
+        checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y");
+    }
+
+    public void testHandlePackageDelete() {
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+        setCaller(CALLING_PACKAGE_1, USER_0);
+        assertTrue(mManager.addDynamicShortcuts(list(
+                makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
+        )));
+
+        setCaller(CALLING_PACKAGE_2, USER_0);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_3, USER_0);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_1, USER_10);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_2, USER_10);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_3, USER_10);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+
+        uninstallPackage(USER_0, CALLING_PACKAGE_1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+
+        uninstallPackage(USER_10, CALLING_PACKAGE_2);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+
+        mInjectedPackages.remove(CALLING_PACKAGE_1);
+        mInjectedPackages.remove(CALLING_PACKAGE_3);
+
+        mService.handleUnlockUser(USER_0);
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+
+        mService.handleUnlockUser(USER_10);
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+    }
+
+    /** Almost ame as testHandlePackageDelete, except it doesn't uninstall packages. */
+    public void testHandlePackageClearData() {
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+        setCaller(CALLING_PACKAGE_1, USER_0);
+        assertTrue(mManager.addDynamicShortcuts(list(
+                makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
+        )));
+
+        setCaller(CALLING_PACKAGE_2, USER_0);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_3, USER_0);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_1, USER_10);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_2, USER_10);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        setCaller(CALLING_PACKAGE_3, USER_10);
+        assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageDataClear(CALLING_PACKAGE_1, USER_0));
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageDataClear(CALLING_PACKAGE_2, USER_10));
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
+        assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
+        assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
+    }
+
+    public void testHandlePackageUpdate() throws Throwable {
+
+        // Set up shortcuts and launchers.
+
+        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcutWithIcon("s2", res32x32),
+                    makeShortcutWithIcon("s3", res32x32),
+                    makeShortcutWithIcon("s4", bmp32x32))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcutWithIcon("s2", bmp32x32))));
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcutWithIcon("s1", res32x32))));
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcutWithIcon("s1", res32x32),
+                    makeShortcutWithIcon("s2", res32x32))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcutWithIcon("s1", bmp32x32),
+                    makeShortcutWithIcon("s2", bmp32x32))));
+        });
+
+        LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
+        LauncherApps.Callback c10 = mock(LauncherApps.Callback.class);
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
+        });
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mLauncherApps.registerCallback(c10, new Handler(Looper.getMainLooper()));
+        });
+
+        mInjectedCurrentTimeMillis = START_TIME + 100;
+
+        ArgumentCaptor<List> shortcuts;
+
+        // First, call the event without updating the versions.
+        reset(c0);
+        reset(c10);
+
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageUpdateIntent(CALLING_PACKAGE_1, USER_10));
+
+        waitOnMainThread();
+
+        // Version not changed, so no callback.
+        verify(c0, times(0)).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                any(List.class),
+                any(UserHandle.class));
+        verify(c10, times(0)).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                any(List.class),
+                any(UserHandle.class));
+
+        // Next, update the version info for package 1.
+        reset(c0);
+        reset(c10);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+
+        // Then send the broadcast, to only user-0.
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
+
+        waitOnMainThread();
+
+        // User-0 should get the notification.
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0));
+
+        // User-10 shouldn't yet get the notification.
+        verify(c10, times(0)).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                any(List.class),
+                any(UserHandle.class));
+        assertShortcutIds(shortcuts.getValue(), "s1", "s2", "s3", "s4");
+        assertEquals(START_TIME,
+                findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
+        assertEquals(START_TIME + 100,
+                findShortcut(shortcuts.getValue(), "s2").getLastChangedTimestamp());
+        assertEquals(START_TIME + 100,
+                findShortcut(shortcuts.getValue(), "s3").getLastChangedTimestamp());
+        assertEquals(START_TIME,
+                findShortcut(shortcuts.getValue(), "s4").getLastChangedTimestamp());
+
+        // Next, send unlock even on user-10.  Now we scan packages on this user and send a
+        // notification to the launcher.
+        mInjectedCurrentTimeMillis = START_TIME + 200;
+
+        doAnswer(new AnswerIsUserRunning(true)).when(mMockUserManager).isUserRunning(eq(USER_10));
+
+        reset(c0);
+        reset(c10);
+        mService.handleUnlockUser(USER_10);
+
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0, times(0)).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                any(List.class),
+                any(UserHandle.class));
+
+        verify(c10).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_10));
+
+        assertShortcutIds(shortcuts.getValue(), "s1", "s2");
+        assertEquals(START_TIME + 200,
+                findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
+        assertEquals(START_TIME + 200,
+                findShortcut(shortcuts.getValue(), "s2").getLastChangedTimestamp());
+
+
+        // Do the same thing for package 2, which doesn't have resource icons.
+        mInjectedCurrentTimeMillis = START_TIME + 300;
+
+        reset(c0);
+        reset(c10);
+        updatePackageVersion(CALLING_PACKAGE_2, 10);
+
+        // Then send the broadcast, to only user-0.
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageUpdateIntent(CALLING_PACKAGE_2, USER_0));
+        mService.handleUnlockUser(USER_10);
+
+        waitOnMainThread();
+
+        verify(c0, times(0)).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                any(List.class),
+                any(UserHandle.class));
+
+        verify(c10, times(0)).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                any(List.class),
+                any(UserHandle.class));
+
+        // Do the same thing for package 3
+        mInjectedCurrentTimeMillis = START_TIME + 400;
+
+        reset(c0);
+        reset(c10);
+        updatePackageVersion(CALLING_PACKAGE_3, 100);
+
+        // Then send the broadcast, to only user-0.
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageUpdateIntent(CALLING_PACKAGE_3, USER_0));
+        mService.handleUnlockUser(USER_10);
+
+        waitOnMainThread();
+
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_3),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0));
+
+        // User 10 doesn't have package 3, so no callback.
+        verify(c10, times(0)).onShortcutsChanged(
+                eq(CALLING_PACKAGE_3),
+                any(List.class),
+                any(UserHandle.class));
+
+        assertShortcutIds(shortcuts.getValue(), "s1");
+        assertEquals(START_TIME + 400,
+                findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
+    }
+
+    /**
+     * Test the case where an updated app has resource IDs changed.
+     */
+    public void testHandlePackageUpdate_resIdChanged() throws Exception {
+        final Icon icon1 = Icon.createWithResource(getTestContext(), /* res ID */ 1000);
+        final Icon icon2 = Icon.createWithResource(getTestContext(), /* res ID */ 1001);
+
+        // Set up shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            // Note resource strings are not officially supported (they're hidden), but
+            // should work.
+
+            final ShortcutInfo s1 = new ShortcutInfo.Builder(mClientContext)
+                    .setId("s1")
+                    .setActivity(makeComponent(ShortcutActivity.class))
+                    .setIntent(new Intent(Intent.ACTION_VIEW))
+                    .setIcon(icon1)
+                    .setTitleResId(10000)
+                    .setTextResId(10001)
+                    .setDisabledMessageResId(10002)
+                    .build();
+
+            final ShortcutInfo s2 = new ShortcutInfo.Builder(mClientContext)
+                    .setId("s2")
+                    .setActivity(makeComponent(ShortcutActivity.class))
+                    .setIntent(new Intent(Intent.ACTION_VIEW))
+                    .setIcon(icon2)
+                    .setTitleResId(20000)
+                    .build();
+
+            assertTrue(mManager.setDynamicShortcuts(list(s1, s2)));
+        });
+
+        // Verify.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            final ShortcutInfo s1 = getCallerShortcut("s1");
+            final ShortcutInfo s2 = getCallerShortcut("s2");
+
+            assertEquals(1000, s1.getIconResourceId());
+            assertEquals(10000, s1.getTitleResId());
+            assertEquals(10001, s1.getTextResId());
+            assertEquals(10002, s1.getDisabledMessageResourceId());
+
+            assertEquals(1001, s2.getIconResourceId());
+            assertEquals(20000, s2.getTitleResId());
+            assertEquals(0, s2.getTextResId());
+            assertEquals(0, s2.getDisabledMessageResourceId());
+        });
+
+        mService.saveDirtyInfo();
+        initService();
+
+        // Set up the mock resources again, with an "adjustment".
+        // When the package is updated, the service will fetch the updated res-IDs with res-names,
+        // and the new IDs will have this offset.
+        setUpAppResources(10);
+
+        // Update the package.
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            final ShortcutInfo s1 = getCallerShortcut("s1");
+            final ShortcutInfo s2 = getCallerShortcut("s2");
+
+            assertEquals(1010, s1.getIconResourceId());
+            assertEquals(10010, s1.getTitleResId());
+            assertEquals(10011, s1.getTextResId());
+            assertEquals(10012, s1.getDisabledMessageResourceId());
+
+            assertEquals(1011, s2.getIconResourceId());
+            assertEquals(20010, s2.getTitleResId());
+            assertEquals(0, s2.getTextResId());
+            assertEquals(0, s2.getDisabledMessageResourceId());
+        });
+    }
+
+    protected void prepareForBackupTest() {
+
+        prepareCrossProfileDataSet();
+
+        backupAndRestore();
+    }
+
+    /**
+     * Make sure the backup data doesn't have the following information:
+     * - Launchers on other users.
+     * - Non-backup app information.
+     *
+     * But restores all other infomation.
+     *
+     * It also omits the following pieces of information, but that's tested in
+     * {@link ShortcutManagerTest2#testShortcutInfoSaveAndLoad_forBackup}.
+     * - Unpinned dynamic shortcuts
+     * - Bitmaps
+     */
+    public void testBackupAndRestore() {
+        prepareForBackupTest();
+
+        checkBackupAndRestore_success();
+    }
+
+    public void testBackupAndRestore_backupRestoreTwice() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        dumpsysOnLogcat("Before second backup");
+
+        backupAndRestore();
+
+        dumpsysOnLogcat("After second backup");
+
+        checkBackupAndRestore_success();
+    }
+
+    public void testBackupAndRestore_backupRestoreMultiple() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        // This also shouldn't affect the result.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+
+        backupAndRestore();
+
+        checkBackupAndRestore_success();
+    }
+
+    public void testBackupAndRestore_restoreToNewVersion() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
+        addPackage(LAUNCHER_1, LAUNCHER_UID_1, 5);
+
+        checkBackupAndRestore_success();
+    }
+
+    public void testBackupAndRestore_restoreToSuperSetSignatures() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        // Change package signatures.
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1);
+        addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy");
+
+        checkBackupAndRestore_success();
+    }
+
+    protected void checkBackupAndRestore_success() {
+        // Make sure non-system user is not restored.
+        final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
+        assertEquals(0, userP0.getAllPackagesForTest().size());
+        assertEquals(0, userP0.getAllLaunchersForTest().size());
+
+        // Make sure only "allowBackup" apps are restored, and are shadow.
+        final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
+        assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1));
+        assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
+        assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_1)));
+        assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_2)));
+
+        assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
+        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
+        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2");
+        });
+
+        installPackage(USER_0, LAUNCHER_1);
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    /* empty, not restored */ );
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty, not restored */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s1", "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty, not restored */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+        // 3 shouldn't be backed up, so no pinned shortcuts.
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        // Launcher on a different profile shouldn't be restored.
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+                            .size());
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+                            .size());
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* wasn't restored, so still empty */ );
+        });
+
+        // Package on a different profile, no restore.
+        installPackage(USER_P0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        // Restore launcher 2 on user 0.
+        installPackage(USER_0, LAUNCHER_2);
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* wasn't restored, so still empty */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+
+        // Restoration of launcher2 shouldn't affect other packages; so do the same checks and
+        // make sure they still have the same result.
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2");
+        });
+
+        installPackage(USER_0, LAUNCHER_1);
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s1", "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* wasn't restored, so still empty */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+    }
+
+    public void testBackupAndRestore_publisherLowerVersion() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
+
+        checkBackupAndRestore_publisherNotRestored();
+    }
+
+    public void testBackupAndRestore_publisherWrongSignature() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
+
+        checkBackupAndRestore_publisherNotRestored();
+    }
+
+    public void testBackupAndRestore_publisherNoLongerBackupTarget() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        updatePackageInfo(CALLING_PACKAGE_1,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+        checkBackupAndRestore_publisherNotRestored();
+    }
+
+    protected void checkBackupAndRestore_publisherNotRestored() {
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        installPackage(USER_0, LAUNCHER_1);
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s1", "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+        installPackage(USER_0, LAUNCHER_2);
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s1", "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+    }
+
+    public void testBackupAndRestore_launcherLowerVersion() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
+
+        checkBackupAndRestore_launcherNotRestored();
+    }
+
+    public void testBackupAndRestore_launcherWrongSignature() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
+
+        checkBackupAndRestore_launcherNotRestored();
+    }
+
+    public void testBackupAndRestore_launcherNoLongerBackupTarget() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        updatePackageInfo(LAUNCHER_1,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+        checkBackupAndRestore_launcherNotRestored();
+    }
+
+    protected void checkBackupAndRestore_launcherNotRestored() {
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+
+            // s1 was pinned by launcher 1, which is not restored, yet, so we still see "s1" here.
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2");
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        // Now we try to restore launcher 1.  Then we realize it's not restorable, so L1 has no pinned
+        // shortcuts.
+        installPackage(USER_0, LAUNCHER_1);
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+
+            // Now CALLING_PACKAGE_1 realizes "s1" is no longer pinned.
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s2");
+        });
+
+        installPackage(USER_0, LAUNCHER_2);
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+    }
+
+    public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        updatePackageInfo(CALLING_PACKAGE_1,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+        updatePackageInfo(LAUNCHER_1,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+        checkBackupAndRestore_publisherAndLauncherNotRestored();
+    }
+
+    protected void checkBackupAndRestore_publisherAndLauncherNotRestored() {
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        installPackage(USER_0, LAUNCHER_1);
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+        installPackage(USER_0, LAUNCHER_2);
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+
+        // Because launcher 1 wasn't restored, "s1" is no longer pinned.
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s2", "s3");
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+    }
+
+    public void testSaveAndLoad_crossProfile() {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5");
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+                    /* empty */);
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_P0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+                    /* empty */);
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "x1", "x2", "x3");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "x4", "x5");
+        });
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s1");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s1", "s2");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s1", "s2", "s3");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s1", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+            assertExpectException(
+                    SecurityException.class, "", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+                    });
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s2");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s2", "s3");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s2", "s3", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s2", "s5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_3, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s3");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s3", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s3", "s4", "s5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s3", "s6");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_4, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s3", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s3", "s4", "s5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s3", "s4", "s5", "s6");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s1", "s4");
+            assertExpectException(
+                    SecurityException.class, "unrelated profile", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+                    });
+        });
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_10),
+                    "x4", "x5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_10)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10)
+                    /* empty */);
+            assertExpectException(
+                    SecurityException.class, "unrelated profile", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0);
+                    });
+            assertExpectException(
+                    SecurityException.class, "unrelated profile", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0);
+                    });
+        });
+    }
+
+    public void testOnApplicationActive_permission() {
+        assertExpectException(SecurityException.class, "Missing permission", () ->
+                mManager.onApplicationActive(CALLING_PACKAGE_1, USER_0));
+
+        // Has permission, now it should pass.
+        mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING);
+        mManager.onApplicationActive(CALLING_PACKAGE_1, USER_0);
+    }
+
+    public void testDumpsys_crossProfile() {
+        prepareCrossProfileDataSet();
+        dumpsysOnLogcat("test1", /* force= */ true);
+    }
+
+    public void testDumpsys_withIcons() throws IOException {
+        testIcons();
+        // 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");
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+                    .haveRanksInOrder("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");
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+                    .haveRanksInOrder("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");
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+                    .haveRanksInOrder("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");
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+                    .haveRanksInOrder("ms1", "ms2");
+            assertShortcutIds(assertAllImmutable(assertAllPinned(
+                    mManager.getPinnedShortcuts())),
+                    "ms2", "ms3");
+            assertAllEnabled(list(getCallerShortcut("ms1")));
+            assertAllEnabled(list(getCallerShortcut("ms2")));
+            assertAllDisabled(list(getCallerShortcut("ms3")));
+        });
+
+        // Multiple activities.
+        // Add shortcuts on activity 2 for package 2.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_5_alt);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()),
+                R.xml.shortcut_5_reverse);
+
+        updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2", "ms3", "ms4", "ms5",
+                    "ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+
+            // Make sure they have the correct ranks, regardless of their ID's alphabetical order.
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+                    .haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()))
+                    .haveRanksInOrder("ms5", "ms4", "ms3", "ms2", "ms1");
+        });
+
+        // Package 2 now has no manifest shortcuts.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+                R.xml.shortcut_0);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.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" + R.string.shortcut_title1, si.getTitleResName());
+            assertEquals(R.string.shortcut_text1, si.getTextResId());
+            assertEquals("r" + R.string.shortcut_text1, si.getTextResName());
+            assertEquals(R.string.shortcut_disabled_message1, si.getDisabledMessageResourceId());
+            assertEquals("r" + R.string.shortcut_disabled_message1, si.getDisabledMessageResName());
+
+            assertEquals(set("android.shortcut.conversation", "android.shortcut.media"),
+                    si.getCategories());
+            assertEquals("action1", si.getIntent().getAction());
+            assertEquals(Uri.parse("http://a.b.c/1"), si.getIntent().getData());
+
+            // check another
+            si = getCallerShortcut("ms2");
+
+            assertEquals("ms2", si.getId());
+            assertEquals(R.drawable.icon2, si.getIconResourceId());
+
+            assertEquals(R.string.shortcut_title2, si.getTitleResId());
+            assertEquals("r" + R.string.shortcut_title2, si.getTitleResName());
+            assertEquals(R.string.shortcut_text2, si.getTextResId());
+            assertEquals("r" + R.string.shortcut_text2, si.getTextResName());
+            assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResourceId());
+            assertEquals("r" + R.string.shortcut_disabled_message2, si.getDisabledMessageResName());
+
+            assertEquals(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("r" + R.string.shortcut_title1, si.getTitleResName());
+
+            assertEquals(0, si.getTextResId());
+            assertEquals(null, si.getTextResName());
+            assertEquals(0, si.getDisabledMessageResourceId());
+            assertEquals(null, si.getDisabledMessageResName());
+
+            assertEquals(null, si.getCategories());
+            assertEquals("android.intent.action.VIEW", si.getIntent().getAction());
+            assertEquals(null, si.getIntent().getData());
+        });
+    }
+
+    public void testManifestShortcuts_localeChange() {
+        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_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.setDynamicShortcuts(list(makeShortcutWithTitle("s1", "title")));
+
+            assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+                    mManager.getManifestShortcuts()))),
+                    "ms1", "ms2");
+
+            // check first shortcut.
+            ShortcutInfo si = getCallerShortcut("ms1");
+
+            assertEquals("ms1", si.getId());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title1 + "/en",
+                    si.getTitle());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text1 + "/en",
+                    si.getText());
+            assertEquals("string-com.android.test.1-user:0-res:"
+                            + R.string.shortcut_disabled_message1 + "/en",
+                    si.getDisabledMessage());
+            assertEquals(START_TIME, si.getLastChangedTimestamp());
+
+            // check another
+            si = getCallerShortcut("ms2");
+
+            assertEquals("ms2", si.getId());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title2 + "/en",
+                    si.getTitle());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text2 + "/en",
+                    si.getText());
+            assertEquals("string-com.android.test.1-user:0-res:"
+                            + R.string.shortcut_disabled_message2 + "/en",
+                    si.getDisabledMessage());
+            assertEquals(START_TIME, si.getLastChangedTimestamp());
+
+            // Check the dynamic one.
+            si = getCallerShortcut("s1");
+
+            assertEquals("s1", si.getId());
+            assertEquals("title", si.getTitle());
+            assertEquals(null, si.getText());
+            assertEquals(null, si.getDisabledMessage());
+            assertEquals(START_TIME, si.getLastChangedTimestamp());
+        });
+
+        mInjectedCurrentTimeMillis++;
+
+        mInjectedLocale = Locale.JAPANESE;
+        mInternal.onSystemLocaleChangedNoLock();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            // check first shortcut.
+            ShortcutInfo si = getCallerShortcut("ms1");
+
+            assertEquals("ms1", si.getId());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title1 + "/ja",
+                    si.getTitle());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text1 + "/ja",
+                    si.getText());
+            assertEquals("string-com.android.test.1-user:0-res:"
+                            + R.string.shortcut_disabled_message1 + "/ja",
+                    si.getDisabledMessage());
+            assertEquals(START_TIME + 1, si.getLastChangedTimestamp());
+
+            // check another
+            si = getCallerShortcut("ms2");
+
+            assertEquals("ms2", si.getId());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title2 + "/ja",
+                    si.getTitle());
+            assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text2 + "/ja",
+                    si.getText());
+            assertEquals("string-com.android.test.1-user:0-res:"
+                            + R.string.shortcut_disabled_message2 + "/ja",
+                    si.getDisabledMessage());
+            assertEquals(START_TIME + 1, si.getLastChangedTimestamp());
+
+            // Check the dynamic one.  (locale change shouldn't affect.)
+            si = getCallerShortcut("s1");
+
+            assertEquals("s1", si.getId());
+            assertEquals("title", si.getTitle());
+            assertEquals(null, si.getText());
+            assertEquals(null, si.getDisabledMessage());
+            assertEquals(START_TIME, si.getLastChangedTimestamp()); // Not changed.
+        });
+    }
+
+    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.getDisabledMessageResourceId());
+            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_5);
+        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", "ms3", "ms4", "ms5");
+
+            // 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());
+            assertEquals(0, si.getRank());
+
+            // 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());
+
+            // Also check the ranks
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()))
+                    .haveRanksInOrder("ms1");
+            assertWith(getCallerShortcuts()).selectManifest()
+                    .selectByActivity(
+                            new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()))
+                    .haveRanksInOrder("ms2", "ms3", "ms4", "ms5");
+
+            // Make sure there's no other dangling shortcuts.
+            assertShortcutIds(getCallerShortcuts(), "ms1", "ms2", "ms3", "ms4", "ms5");
+        });
+    }
+
+    /**
+     * 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());
+        });
+    }
+
+    protected 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();
+    }
+
+
+    /**
+     * Make sure the APIs won't work on manifest shortcuts.
+     */
+    public void testManifestShortcuts_tooMany() {
+        // Change the max number of shortcuts.
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
+        mService.handleUnlockUser(USER_0);
+
+        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, () -> {
+            // Only the first 3 should be published.
+            assertShortcutIds(mManager.getManifestShortcuts(), "ms1", "ms2", "ms3");
+        });
+    }
+
+    public void testMaxShortcutCount_set() {
+        // Change the max number of shortcuts.
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
+            final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
+            final ShortcutInfo s1_1 = makeShortcutWithActivity("s11", a1);
+            final ShortcutInfo s1_2 = makeShortcutWithActivity("s12", a1);
+            final ShortcutInfo s1_3 = makeShortcutWithActivity("s13", a1);
+            final ShortcutInfo s1_4 = makeShortcutWithActivity("s14", a1);
+            final ShortcutInfo s1_5 = makeShortcutWithActivity("s15", a1);
+            final ShortcutInfo s1_6 = makeShortcutWithActivity("s16", a1);
+            final ShortcutInfo s2_1 = makeShortcutWithActivity("s21", a2);
+            final ShortcutInfo s2_2 = makeShortcutWithActivity("s22", a2);
+            final ShortcutInfo s2_3 = makeShortcutWithActivity("s23", a2);
+            final ShortcutInfo s2_4 = makeShortcutWithActivity("s24", a2);
+
+            // 3 shortcuts for 2 activities -> okay
+            mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21", "s22", "s23");
+
+            mManager.removeAllDynamicShortcuts();
+
+            // 4 shortcut for activity 1 -> too many.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s1_4, s2_1, s2_2, s2_3));
+            });
+            assertEmpty(mManager.getDynamicShortcuts());
+
+            // 4 shortcut for activity 2 -> too many.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3, s2_4));
+            });
+            assertEmpty(mManager.getDynamicShortcuts());
+
+            // First, set 3.  Then set 4, which should be ignored.
+            mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13");
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.setDynamicShortcuts(list(s2_1, s2_2, s2_3, s2_4));
+            });
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13");
+
+            // Set will remove the old dynamic set, unlike add, so the following should pass.
+            mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13");
+            mManager.setDynamicShortcuts(list(s1_4, s1_5, s1_6));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s14", "s15", "s16");
+
+            // Now, test with 2 manifest shortcuts.
+            mManager.removeAllDynamicShortcuts();
+            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));
+            assertEquals(2, mManager.getManifestShortcuts().size());
+
+            // Setting 1 to activity 1 will work.
+            mManager.setDynamicShortcuts(list(s1_1, s2_1, s2_2, s2_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s21", "s22", "s23");
+            assertEquals(2, mManager.getManifestShortcuts().size());
+
+            // But setting 2 will not.
+            mManager.removeAllDynamicShortcuts();
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.setDynamicShortcuts(list(s1_1, s1_2, s2_1, s2_2, s2_3));
+            });
+            assertEmpty(mManager.getDynamicShortcuts());
+            assertEquals(2, mManager.getManifestShortcuts().size());
+        });
+    }
+
+    public void testMaxShortcutCount_add() {
+        // Change the max number of shortcuts.
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
+            final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
+            final ShortcutInfo s1_1 = makeShortcutWithActivity("s11", a1);
+            final ShortcutInfo s1_2 = makeShortcutWithActivity("s12", a1);
+            final ShortcutInfo s1_3 = makeShortcutWithActivity("s13", a1);
+            final ShortcutInfo s1_4 = makeShortcutWithActivity("s14", a1);
+            final ShortcutInfo s2_1 = makeShortcutWithActivity("s21", a2);
+            final ShortcutInfo s2_2 = makeShortcutWithActivity("s22", a2);
+            final ShortcutInfo s2_3 = makeShortcutWithActivity("s23", a2);
+            final ShortcutInfo s2_4 = makeShortcutWithActivity("s24", a2);
+
+            // 3 shortcuts for 2 activities -> okay
+            mManager.addDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21", "s22", "s23");
+
+            mManager.removeAllDynamicShortcuts();
+
+            // 4 shortcut for activity 1 -> too many.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.addDynamicShortcuts(list(s1_1, s1_2, s1_3, s1_4, s2_1, s2_2, s2_3));
+            });
+            assertEmpty(mManager.getDynamicShortcuts());
+
+            // 4 shortcut for activity 2 -> too many.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.addDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3, s2_4));
+            });
+            assertEmpty(mManager.getDynamicShortcuts());
+
+            // First, set 3.  Then add 1 more, which should be ignored.
+            mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13");
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.addDynamicShortcuts(list(s1_4, s2_1));
+            });
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13");
+
+            // Update existing one, which should work.
+            mManager.addDynamicShortcuts(list(makeShortcutWithActivityAndTitle(
+                    "s11", a1, "xxx"), s2_1));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21");
+            assertEquals("xxx", getCallerShortcut("s11").getTitle());
+
+            // Make sure pinned shortcuts won't affect.
+            // - Pin s11 - s13, and remove all dynamic.
+            runWithCaller(LAUNCHER_1, USER_0, () -> {
+                mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s11", "s12", "s13"),
+                        HANDLE_USER_0);
+            });
+            mManager.removeAllDynamicShortcuts();
+
+            assertEmpty(mManager.getDynamicShortcuts());
+            assertShortcutIds(mManager.getPinnedShortcuts(),
+                    "s11", "s12", "s13");
+
+            // Then add dynamic.
+            mManager.addDynamicShortcuts(list(s1_4, s2_1, s2_2, s2_3));
+
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s14", "s21", "s22", "s23");
+            assertShortcutIds(mManager.getPinnedShortcuts(),
+                    "s11", "s12", "s13");
+
+            // Adding "s11" and "s12" back, should work
+            mManager.addDynamicShortcuts(list(s1_1, s1_2));
+
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s14", "s11", "s12", "s21", "s22", "s23");
+            assertShortcutIds(mManager.getPinnedShortcuts(),
+                    "s11", "s12", "s13");
+
+            // Adding back s13 doesn't work.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.addDynamicShortcuts(list(s1_3));
+            });
+
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
+                    "s11", "s12", "s14");
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
+                    "s21", "s22", "s23");
+
+            // Now swap the activities.
+            mManager.updateShortcuts(list(
+                    makeShortcutWithActivity("s11", a2),
+                    makeShortcutWithActivity("s21", a1)));
+
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
+                    "s21", "s12", "s14");
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
+                    "s11", "s22", "s23");
+
+            // Now, test with 2 manifest shortcuts.
+            mManager.removeAllDynamicShortcuts();
+            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));
+
+            assertEquals(2, mManager.getManifestShortcuts().size());
+
+            // Adding one shortcut to activity 1 works fine.
+            mManager.addDynamicShortcuts(list(s1_1, s2_1, s2_2, s2_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s21", "s22", "s23");
+            assertEquals(2, mManager.getManifestShortcuts().size());
+
+            // But adding one more doesn't.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.addDynamicShortcuts(list(s1_4, s2_1));
+            });
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s21", "s22", "s23");
+            assertEquals(2, mManager.getManifestShortcuts().size());
+        });
+    }
+
+    public void testMaxShortcutCount_update() {
+        // Change the max number of shortcuts.
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
+            final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
+            final ShortcutInfo s1_1 = makeShortcutWithActivity("s11", a1);
+            final ShortcutInfo s1_2 = makeShortcutWithActivity("s12", a1);
+            final ShortcutInfo s1_3 = makeShortcutWithActivity("s13", a1);
+            final ShortcutInfo s1_4 = makeShortcutWithActivity("s14", a1);
+            final ShortcutInfo s1_5 = makeShortcutWithActivity("s15", a1);
+            final ShortcutInfo s2_1 = makeShortcutWithActivity("s21", a2);
+            final ShortcutInfo s2_2 = makeShortcutWithActivity("s22", a2);
+            final ShortcutInfo s2_3 = makeShortcutWithActivity("s23", a2);
+            final ShortcutInfo s2_4 = makeShortcutWithActivity("s24", a2);
+
+            // 3 shortcuts for 2 activities -> okay
+            mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21", "s22", "s23");
+
+            // Trying to move s11 from a1 to a2 should fail.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.updateShortcuts(list(makeShortcutWithActivity("s11", a2)));
+            });
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21", "s22", "s23");
+
+            // Trying to move s21 from a2 to a1 should also fail.
+            assertDynamicShortcutCountExceeded(() -> {
+                mManager.updateShortcuts(list(makeShortcutWithActivity("s21", a1)));
+            });
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21", "s22", "s23");
+
+            // But, if we do these two at the same time, it should work.
+            mManager.updateShortcuts(list(
+                    makeShortcutWithActivity("s11", a2),
+                    makeShortcutWithActivity("s21", a1)));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21", "s22", "s23");
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
+                    "s21", "s12", "s13");
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
+                    "s11", "s22", "s23");
+
+            // Then reset.
+            mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s11", "s12", "s13", "s21", "s22", "s23");
+
+            // Pin some to have more shortcuts for a1.
+            runWithCaller(LAUNCHER_1, USER_0, () -> {
+                mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s11", "s12", "s13"),
+                        HANDLE_USER_0);
+            });
+            mManager.setDynamicShortcuts(list(s1_4, s1_5, s2_1, s2_2, s2_3));
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s14", "s15", "s21", "s22", "s23");
+            assertShortcutIds(mManager.getPinnedShortcuts(),
+                    "s11", "s12", "s13");
+
+            // a1 already has 2 dynamic shortcuts (and 3 pinned shortcuts that used to belong on it)
+            // But that doesn't matter for update -- the following should still work.
+            mManager.updateShortcuts(list(
+                    makeShortcutWithActivityAndTitle("s11", a1, "xxx1"),
+                    makeShortcutWithActivityAndTitle("s12", a1, "xxx2"),
+                    makeShortcutWithActivityAndTitle("s13", a1, "xxx3"),
+                    makeShortcutWithActivityAndTitle("s14", a1, "xxx4"),
+                    makeShortcutWithActivityAndTitle("s15", a1, "xxx5")));
+            // All the shortcuts should still exist they all belong on same activities,
+            // with the updated titles.
+            assertShortcutIds(mManager.getDynamicShortcuts(),
+                    "s14", "s15", "s21", "s22", "s23");
+            assertShortcutIds(mManager.getPinnedShortcuts(),
+                    "s11", "s12", "s13");
+
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
+                    "s14", "s15");
+            assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
+                    "s21", "s22", "s23");
+
+            assertEquals("xxx1", getCallerShortcut("s11").getTitle());
+            assertEquals("xxx2", getCallerShortcut("s12").getTitle());
+            assertEquals("xxx3", getCallerShortcut("s13").getTitle());
+            assertEquals("xxx4", getCallerShortcut("s14").getTitle());
+            assertEquals("xxx5", getCallerShortcut("s15").getTitle());
+        });
+    }
+
+    public void testShortcutsPushedOutByManifest() {
+        // Change the max number of shortcuts.
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
+            final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
+            final ShortcutInfo s1_1 = makeShortcutWithActivityAndRank("s11", a1, 4);
+            final ShortcutInfo s1_2 = makeShortcutWithActivityAndRank("s12", a1, 3);
+            final ShortcutInfo s1_3 = makeShortcutWithActivityAndRank("s13", a1, 2);
+            final ShortcutInfo s1_4 = makeShortcutWithActivityAndRank("s14", a1, 1);
+            final ShortcutInfo s1_5 = makeShortcutWithActivityAndRank("s15", a1, 0);
+            final ShortcutInfo s2_1 = makeShortcutWithActivityAndRank("s21", a2, 0);
+            final ShortcutInfo s2_2 = makeShortcutWithActivityAndRank("s22", a2, 1);
+            final ShortcutInfo s2_3 = makeShortcutWithActivityAndRank("s23", a2, 2);
+            final ShortcutInfo s2_4 = makeShortcutWithActivityAndRank("s24", a2, 3);
+            final ShortcutInfo s2_5 = makeShortcutWithActivityAndRank("s25", a2, 4);
+
+            // Initial state.
+            mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
+            runWithCaller(LAUNCHER_1, USER_0, () -> {
+                mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s11", "s12", "s21", "s22"),
+                        HANDLE_USER_0);
+            });
+            mManager.setDynamicShortcuts(list(s1_2, s1_3, s1_4, s2_2, s2_3, s2_4));
+            assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
+                    "s12", "s13", "s14",
+                    "s22", "s23", "s24");
+            assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
+                    "s11", "s12",
+                    "s21", "s22");
+
+            // Add 1 manifest shortcut to a1.
+            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));
+            assertEquals(1, mManager.getManifestShortcuts().size());
+
+            // s12 removed.
+            assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
+                    "s13", "s14",
+                    "s22", "s23", "s24");
+            assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
+                    "s11", "s12",
+                    "s21", "s22");
+
+            // Add more manifest shortcuts.
+            addManifestShortcutResource(
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                    R.xml.shortcut_2);
+            addManifestShortcutResource(
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
+                    R.xml.shortcut_1_alt);
+            updatePackageVersion(CALLING_PACKAGE_1, 1);
+            mService.mPackageMonitor.onReceive(getTestContext(),
+                    genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+            assertEquals(3, mManager.getManifestShortcuts().size());
+
+            // Note the ones with the highest rank values (== least important) will be removed.
+            assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
+                    "s14",
+                    "s22", "s23");
+            assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
+                    "s11", "s12",
+                    "s21", "s22");
+
+            // Add more manifest shortcuts.
+            addManifestShortcutResource(
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                    R.xml.shortcut_2);
+            addManifestShortcutResource(
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
+                    R.xml.shortcut_5_alt); // manifest has 5, but max is 3, so a2 will have 3.
+            updatePackageVersion(CALLING_PACKAGE_1, 1);
+            mService.mPackageMonitor.onReceive(getTestContext(),
+                    genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+            assertEquals(5, mManager.getManifestShortcuts().size());
+
+            assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
+                    "s14" // a1 has 1 dynamic
+            ); // a2 has no dynamic
+            assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
+                    "s11", "s12",
+                    "s21", "s22");
+
+            // Update, no manifest shortucts.  This doesn't affect anything.
+            addManifestShortcutResource(
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                    R.xml.shortcut_0);
+            addManifestShortcutResource(
+                    new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
+                    R.xml.shortcut_0);
+            updatePackageVersion(CALLING_PACKAGE_1, 1);
+            mService.mPackageMonitor.onReceive(getTestContext(),
+                    genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+            assertEquals(0, mManager.getManifestShortcuts().size());
+
+            assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
+                    "s14");
+            assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
+                    "s11", "s12",
+                    "s21", "s22");
+        });
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
new file mode 100644
index 0000000..428b872
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -0,0 +1,1695 @@
+/*
+ * 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 static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.parceled;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest.permission;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.frameworks.servicestests.R;
+
+/**
+ * Tests for ShortcutService and ShortcutManager.
+ *
+ m FrameworksServicesTests &&
+ adb install \
+ -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest2 \
+ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
+    // ShortcutInfo tests
+
+    public void testShortcutInfoMissingMandatoryFields() {
+        // Disable throttling.
+        mService.updateConfigurationLocked(
+                ShortcutService.ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
+                + ShortcutService.ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
+        );
+
+        assertExpectException(
+                IllegalArgumentException.class,
+                "ID must be provided",
+                () -> new ShortcutInfo.Builder(getTestContext()).build());
+
+        assertExpectException(NullPointerException.class, "Intent action must be set",
+                () -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
+
+        assertExpectException(NullPointerException.class, "Activity must be provided", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext()).setId("id").build();
+            assertTrue(getManager().setDynamicShortcuts(list(si)));
+        });
+
+        assertExpectException(
+                IllegalArgumentException.class, "Short label must be provided", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
+                    .setId("id")
+                    .setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
+                    .build();
+            assertTrue(getManager().setDynamicShortcuts(list(si)));
+        });
+
+        assertExpectException(
+                IllegalArgumentException.class, "Short label must be provided", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
+                    .setId("id")
+                    .setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
+                    .build();
+            assertTrue(getManager().addDynamicShortcuts(list(si)));
+        });
+
+        assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
+                    .setId("id")
+                    .setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
+                    .setShortLabel("x")
+                    .build();
+            assertTrue(getManager().setDynamicShortcuts(list(si)));
+        });
+
+        assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
+                    .setId("id")
+                    .setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
+                    .setShortLabel("x")
+                    .build();
+            assertTrue(getManager().addDynamicShortcuts(list(si)));
+        });
+
+        assertExpectException(
+                IllegalStateException.class, "package name mismatch", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
+                    .setId("id")
+                    .setActivity(new ComponentName("xxx", "s"))
+                    .build();
+            assertTrue(getManager().setDynamicShortcuts(list(si)));
+        });
+
+        assertExpectException(
+                IllegalStateException.class, "package name mismatch", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
+                    .setId("id")
+                    .setActivity(new ComponentName("xxx", "s"))
+                    .build();
+            assertTrue(getManager().addDynamicShortcuts(list(si)));
+        });
+    }
+
+    public void testShortcutInfoParcel() {
+        setCaller(CALLING_PACKAGE_1, USER_10);
+        ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setTitle("title")
+                .setIntent(makeIntent("action", ShortcutActivity.class))
+                .build());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals(USER_10, si.getUserId());
+        assertEquals(HANDLE_USER_10, si.getUserHandle());
+        assertEquals("id", si.getId());
+        assertEquals("title", si.getTitle());
+        assertEquals("action", si.getIntent().getAction());
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+
+        si = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setActivity(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
+                .setTitle("title")
+                .setText("text")
+                .setDisabledMessage("dismes")
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+        si.addFlags(ShortcutInfo.FLAG_PINNED);
+        si.setBitmapPath("abc");
+        si.setIconResourceId(456);
+
+        si = parceled(si);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(123, si.getIcon().getResId());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+
+        assertEquals(0, si.getTitleResId());
+        assertEquals(null, si.getTitleResName());
+        assertEquals(0, si.getTextResId());
+        assertEquals(null, si.getTextResName());
+        assertEquals(0, si.getDisabledMessageResourceId());
+        assertEquals(null, si.getDisabledMessageResName());
+    }
+
+    public void testShortcutInfoParcel_resId() {
+        setCaller(CALLING_PACKAGE_1, USER_10);
+        ShortcutInfo si;
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+
+        si = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setActivity(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
+                .setTitleResId(10)
+                .setTextResId(11)
+                .setDisabledMessageResId(12)
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+        si.addFlags(ShortcutInfo.FLAG_PINNED);
+        si.setBitmapPath("abc");
+        si.setIconResourceId(456);
+
+        lookupAndFillInResourceNames(si);
+
+        si = parceled(si);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(123, si.getIcon().getResId());
+        assertEquals(10, si.getTitleResId());
+        assertEquals("r10", si.getTitleResName());
+        assertEquals(11, si.getTextResId());
+        assertEquals("r11", si.getTextResName());
+        assertEquals(12, si.getDisabledMessageResourceId());
+        assertEquals("r12", si.getDisabledMessageResName());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+        assertEquals("string/r456", si.getIconResName());
+    }
+
+    public void testShortcutInfoClone() {
+        setCaller(CALLING_PACKAGE_1, USER_11);
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivity(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
+                .setTitle("title")
+                .setText("text")
+                .setDisabledMessage("dismes")
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        lookupAndFillInResourceNames(sorig);
+
+        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+        assertEquals(USER_11, si.getUserId());
+        assertEquals(HANDLE_USER_11, si.getUserHandle());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(123, si.getIcon().getResId());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+        assertEquals("string/r456", si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals(null, si.getIntent());
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(null, si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals(null, si.getTitle());
+        assertEquals(null, si.getText());
+        assertEquals(null, si.getDisabledMessage());
+        assertEquals(null, si.getCategories());
+        assertEquals(null, si.getIntent());
+        assertEquals(0, si.getRank());
+        assertEquals(null, si.getExtras());
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+    }
+
+    public void testShortcutInfoClone_resId() {
+        setCaller(CALLING_PACKAGE_1, USER_11);
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivity(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
+                .setTitleResId(10)
+                .setTextResId(11)
+                .setDisabledMessageResId(12)
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        lookupAndFillInResourceNames(sorig);
+
+        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+        assertEquals(USER_11, si.getUserId());
+        assertEquals(HANDLE_USER_11, si.getUserHandle());
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(123, si.getIcon().getResId());
+        assertEquals(10, si.getTitleResId());
+        assertEquals("r10", si.getTitleResName());
+        assertEquals(11, si.getTextResId());
+        assertEquals("r11", si.getTextResName());
+        assertEquals(12, si.getDisabledMessageResourceId());
+        assertEquals("r12", si.getDisabledMessageResName());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+        assertEquals("string/r456", si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals(10, si.getTitleResId());
+        assertEquals(null, si.getTitleResName());
+        assertEquals(11, si.getTextResId());
+        assertEquals(null, si.getTextResName());
+        assertEquals(12, si.getDisabledMessageResourceId());
+        assertEquals(null, si.getDisabledMessageResName());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals(10, si.getTitleResId());
+        assertEquals(null, si.getTitleResName());
+        assertEquals(11, si.getTextResId());
+        assertEquals(null, si.getTextResName());
+        assertEquals(12, si.getDisabledMessageResourceId());
+        assertEquals(null, si.getDisabledMessageResName());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals(null, si.getIntent());
+        assertEquals(123, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(null, si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals(0, si.getTitleResId());
+        assertEquals(null, si.getTitleResName());
+        assertEquals(0, si.getTextResId());
+        assertEquals(null, si.getTextResName());
+        assertEquals(0, si.getDisabledMessageResourceId());
+        assertEquals(null, si.getDisabledMessageResName());
+        assertEquals(null, si.getCategories());
+        assertEquals(null, si.getIntent());
+        assertEquals(0, si.getRank());
+        assertEquals(null, si.getExtras());
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+    }
+
+    public void testShortcutInfoClone_minimum() {
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setTitle("title")
+                .setIntent(makeIntent("action", ShortcutActivity.class))
+                .build();
+        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals("title", si.getTitle());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals(null, si.getCategories());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals("title", si.getTitle());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals(null, si.getCategories());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals("title", si.getTitle());
+        assertEquals(null, si.getIntent());
+        assertEquals(null, si.getCategories());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(null, si.getTitle());
+        assertEquals(null, si.getIntent());
+        assertEquals(null, si.getCategories());
+    }
+
+    public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException {
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setActivity(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
+                .setTitle("title")
+                .setText("text")
+                .setDisabledMessage("dismes")
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        lookupAndFillInResourceNames(sorig);
+
+        ShortcutInfo si;
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setActivity(new ComponentName("x", "y")).build());
+        assertEquals("text", si.getText());
+        assertEquals(123, si.getRank());
+        assertEquals(new ComponentName("x", "y"), si.getActivity());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIcon(Icon.createWithResource(mClientContext, 456)).build());
+        assertEquals("text", si.getText());
+        assertEquals(456, si.getIcon().getResId());
+        assertEquals(0, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+        assertEquals(null, si.getBitmapPath());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTitle("xyz").build());
+        assertEquals("text", si.getText());
+        assertEquals("xyz", si.getTitle());
+        assertEquals(0, si.getTitleResId());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTitleResId(123).build());
+        assertEquals("text", si.getText());
+        assertEquals(null, si.getTitle());
+        assertEquals(123, si.getTitleResId());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setText("xxx").build());
+        assertEquals(123, si.getRank());
+        assertEquals("xxx", si.getText());
+        assertEquals(0, si.getTextResId());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTextResId(1111).build());
+        assertEquals(123, si.getRank());
+        assertEquals(null, si.getText());
+        assertEquals(1111, si.getTextResId());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setDisabledMessage("xxx").build());
+        assertEquals(123, si.getRank());
+        assertEquals("xxx", si.getDisabledMessage());
+        assertEquals(0, si.getDisabledMessageResourceId());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setDisabledMessageResId(11111).build());
+        assertEquals(123, si.getRank());
+        assertEquals(null, si.getDisabledMessage());
+        assertEquals(11111, si.getDisabledMessageResourceId());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setCategories(set()).build());
+        assertEquals("text", si.getText());
+        assertEquals(set(), si.getCategories());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setCategories(set("x")).build());
+        assertEquals("text", si.getText());
+        assertEquals(set("x"), si.getCategories());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
+        assertEquals("text", si.getText());
+        assertEquals("action2", si.getIntent().getAction());
+        assertEquals(null, si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
+        assertEquals("text", si.getText());
+        assertEquals("action3", si.getIntent().getAction());
+        assertEquals("x", si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setRank(999).build());
+        assertEquals("text", si.getText());
+        assertEquals(999, si.getRank());
+
+
+        PersistableBundle pb2 = new PersistableBundle();
+        pb2.putInt("x", 99);
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setExtras(pb2).build());
+        assertEquals("text", si.getText());
+        assertEquals(99, si.getExtras().getInt("x"));
+    }
+
+    public void testShortcutInfoCopyNonNullFieldsFrom_resId() throws InterruptedException {
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setActivity(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithResource(mClientContext, 123))
+                .setTitleResId(10)
+                .setTextResId(11)
+                .setDisabledMessageResId(12)
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        ShortcutInfo si;
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setActivity(new ComponentName("x", "y")).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals(new ComponentName("x", "y"), si.getActivity());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIcon(Icon.createWithResource(mClientContext, 456)).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals(456, si.getIcon().getResId());
+        assertEquals(0, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+        assertEquals(null, si.getBitmapPath());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTitle("xyz").build());
+        assertEquals(11, si.getTextResId());
+        assertEquals("xyz", si.getTitle());
+        assertEquals(0, si.getTitleResId());
+        assertEquals(null, si.getTitleResName());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTitleResId(123).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals(null, si.getTitle());
+        assertEquals(123, si.getTitleResId());
+        assertEquals(null, si.getTitleResName());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setText("xxx").build());
+        assertEquals(123, si.getRank());
+        assertEquals("xxx", si.getText());
+        assertEquals(0, si.getTextResId());
+        assertEquals(null, si.getTextResName());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTextResId(1111).build());
+        assertEquals(123, si.getRank());
+        assertEquals(null, si.getText());
+        assertEquals(1111, si.getTextResId());
+        assertEquals(null, si.getTextResName());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setDisabledMessage("xxx").build());
+        assertEquals(123, si.getRank());
+        assertEquals("xxx", si.getDisabledMessage());
+        assertEquals(0, si.getDisabledMessageResourceId());
+        assertEquals(null, si.getDisabledMessageResName());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setDisabledMessageResId(11111).build());
+        assertEquals(123, si.getRank());
+        assertEquals(null, si.getDisabledMessage());
+        assertEquals(11111, si.getDisabledMessageResourceId());
+        assertEquals(null, si.getDisabledMessageResName());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setCategories(set()).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals(set(), si.getCategories());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setCategories(set("x")).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals(set("x"), si.getCategories());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals("action2", si.getIntent().getAction());
+        assertEquals(null, si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals("action3", si.getIntent().getAction());
+        assertEquals("x", si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setRank(999).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals(999, si.getRank());
+
+
+        PersistableBundle pb2 = new PersistableBundle();
+        pb2.putInt("x", 99);
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setExtras(pb2).build());
+        assertEquals(11, si.getTextResId());
+        assertEquals(99, si.getExtras().getInt("x"));
+    }
+
+    public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+        setCaller(CALLING_PACKAGE_1, USER_10);
+
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIcon(bmp32x32)
+                .setTitle("title")
+                .setText("text")
+                .setDisabledMessage("dismes")
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+
+        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+                .setId("id2")
+                .setTitle("x")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(456)
+                .build();
+
+        mManager.addDynamicShortcuts(list(sorig, sorig2));
+
+        Thread.sleep(2);
+        final long now = System.currentTimeMillis();
+
+        // Save and load.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_10);
+
+        ShortcutInfo si;
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);
+
+        assertEquals(USER_10, si.getUserId());
+        assertEquals(HANDLE_USER_10, si.getUserHandle());
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(0, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE
+                | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+        assertNotNull(si.getBitmapPath()); // Something should be set.
+        assertEquals(0, si.getIconResourceId());
+        assertTrue(si.getLastChangedTimestamp() < now);
+
+        // Make sure ranks are saved too.  Because of the auto-adjusting, we need two shortcuts
+        // to test it.
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
+        assertEquals(1, si.getRank());
+    }
+
+    public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
+        setCaller(CALLING_PACKAGE_1, USER_10);
+
+        final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIcon(res32x32)
+                .setTitleResId(10)
+                .setTextResId(11)
+                .setDisabledMessageResId(12)
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+
+        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+                .setId("id2")
+                .setTitle("x")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(456)
+                .build();
+
+        mManager.addDynamicShortcuts(list(sorig, sorig2));
+
+        Thread.sleep(2);
+        final long now = System.currentTimeMillis();
+
+        // Save and load.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_10);
+
+        ShortcutInfo si;
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);
+
+        assertEquals(USER_10, si.getUserId());
+        assertEquals(HANDLE_USER_10, si.getUserHandle());
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
+        assertEquals(null, si.getIcon());
+        assertEquals(10, si.getTitleResId());
+        assertEquals("r10", si.getTitleResName());
+        assertEquals(11, si.getTextResId());
+        assertEquals("r11", si.getTextResName());
+        assertEquals(12, si.getDisabledMessageResourceId());
+        assertEquals("r12", si.getDisabledMessageResName());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(0, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_RES
+                | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+        assertNull(si.getBitmapPath());
+        assertEquals(R.drawable.black_32x32, si.getIconResourceId());
+        assertTrue(si.getLastChangedTimestamp() < now);
+
+        // Make sure ranks are saved too.  Because of the auto-adjusting, we need two shortcuts
+        // to test it.
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
+        assertEquals(1, si.getRank());
+    }
+
+    public void testShortcutInfoSaveAndLoad_forBackup() {
+        setCaller(CALLING_PACKAGE_1, USER_0);
+
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIcon(bmp32x32)
+                .setTitle("title")
+                .setText("text")
+                .setDisabledMessage("dismes")
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+
+        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+                .setId("id2")
+                .setTitle("x")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(456)
+                .build();
+
+        mManager.addDynamicShortcuts(list(sorig, sorig2));
+
+        // Dynamic shortcuts won't be backed up, so we need to pin it.
+        setCaller(LAUNCHER_1, USER_0);
+        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);
+
+        // Do backup & restore.
+        backupAndRestore();
+
+        mService.handleUnlockUser(USER_0); // Load user-0.
+
+        ShortcutInfo si;
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(0, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+        assertNull(si.getBitmapPath()); // No icon.
+        assertEquals(0, si.getIconResourceId());
+
+        // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
+        assertEquals(0, si.getRank());
+    }
+
+    public void testShortcutInfoSaveAndLoad_forBackup_resId() {
+        setCaller(CALLING_PACKAGE_1, USER_0);
+
+        final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIcon(res32x32)
+                .setTitleResId(10)
+                .setTextResId(11)
+                .setDisabledMessageResId(12)
+                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(123)
+                .setExtras(pb)
+                .build();
+
+        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+                .setId("id2")
+                .setTitle("x")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setRank(456)
+                .build();
+
+        mManager.addDynamicShortcuts(list(sorig, sorig2));
+
+        // Dynamic shortcuts won't be backed up, so we need to pin it.
+        setCaller(LAUNCHER_1, USER_0);
+        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);
+
+        // Do backup & restore.
+        backupAndRestore();
+
+        mService.handleUnlockUser(USER_0); // Load user-0.
+
+        ShortcutInfo si;
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+        assertEquals(CALLING_PACKAGE_1, si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
+        assertEquals(null, si.getIcon());
+        assertEquals(10, si.getTitleResId());
+        assertEquals("r10", si.getTitleResName());
+        assertEquals(11, si.getTextResId());
+        assertEquals("r11", si.getTextResName());
+        assertEquals(12, si.getDisabledMessageResourceId());
+        assertEquals("r12", si.getDisabledMessageResName());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(0, si.getRank());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+        assertNull(si.getBitmapPath()); // No icon.
+        assertEquals(0, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
+
+        // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
+        assertEquals(0, si.getRank());
+    }
+
+    public void testThrottling() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(1, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        // Reached the max
+
+        mInjectedCurrentTimeMillis++;
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        // Still throttled
+        mInjectedCurrentTimeMillis = START_TIME + INTERVAL - 1;
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        // Now it should work.
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1))); // fail
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(1, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis++;
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
+
+        // 4 hours later...
+        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(1, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());
+
+        // Make sure getRemainingCallCount() itself gets reset without calling setDynamicShortcuts().
+        mInjectedCurrentTimeMillis = START_TIME + 8 * INTERVAL;
+        assertEquals(3, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
+    }
+
+    public void testThrottling_rewind() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeMillis = 12345; // Clock reset!
+
+        // Since the clock looks invalid, the counter shouldn't have reset.
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        // Forward again.  Still haven't reset yet.
+        mInjectedCurrentTimeMillis = START_TIME + INTERVAL - 1;
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        // Now rewind -- this will reset the counters.
+        mInjectedCurrentTimeMillis = START_TIME - 100000;
+        assertEquals(3, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        // Forward again, should be reset now.
+        mInjectedCurrentTimeMillis += INTERVAL;
+        assertEquals(3, mManager.getRemainingCallCount());
+    }
+
+    public void testThrottling_perPackage() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // Reached the max
+
+        mInjectedCurrentTimeMillis++;
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
+
+        // Try from a different caller.
+        mInjectedClientPackage = CALLING_PACKAGE_2;
+        mInjectedCallingUid = CALLING_UID_2;
+
+        // Need to create a new one wit the updated package name.
+        final ShortcutInfo si2 = makeShortcut("shortcut1");
+
+        assertEquals(3, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        // Back to the original caller, still throttled.
+        mInjectedClientPackage = CALLING_PACKAGE_1;
+        mInjectedCallingUid = CALLING_UID_1;
+
+        mInjectedCurrentTimeMillis = START_TIME + INTERVAL - 1;
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // Now it should work.
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+
+        mInjectedCurrentTimeMillis++;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+
+        mInjectedCurrentTimeMillis++;
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
+
+        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL;
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
+
+        mInjectedClientPackage = CALLING_PACKAGE_2;
+        mInjectedCallingUid = CALLING_UID_2;
+
+        assertEquals(3, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertFalse(mManager.setDynamicShortcuts(list(si2)));
+    }
+
+    public void testThrottling_localeChanges() {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        final long origSequenceNumber = mService.getLocaleChangeSequenceNumber();
+
+        mInternal.onSystemLocaleChangedNoLock();
+        assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber());
+
+        // Note at this point only user-0 is loaded, and the counters are reset for this user,
+        // but it will work for other users too, because we persist when
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+
+        mService.saveDirtyInfo();
+        initService();
+
+        // Make sure the counter is persisted.
+        assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber());
+    }
+
+    public void testThrottling_foreground() throws Exception {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        // We need to update the current time from time to time, since some of the internal checks
+        // rely on the time being correctly incremented.
+        mInjectedCurrentTimeMillis++;
+
+        // First, all packages have less than 3 (== initial value) remaining calls.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeMillis++;
+
+        // State changed, but not foreground, so no resetting.
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeMillis++;
+
+        // State changed, package1 foreground, reset.
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_1, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+
+        mInjectedCurrentTimeMillis++;
+
+        // Different app comes to foreground briefly, and goes back to background.
+        // Now, make sure package 2's counter is reset, even in this case.
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeMillis++;
+
+        // Do the same thing one more time.  This would catch the bug with mixuing up
+        // the current time and the elapsed time.
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            mManager.updateShortcuts(list(makeShortcut("s")));
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.mUidObserver.onUidStateChanged(
+                CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mInjectedCurrentTimeMillis++;
+
+        // Package 1 on user-10 comes to foreground.
+        // Now, also try calling some APIs and make sure foreground apps don't get throttled.
+        mService.mUidObserver.onUidStateChanged(
+                UserHandle.getUid(USER_10, CALLING_UID_1),
+                ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(0, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+            mManager.setDynamicShortcuts(list(makeShortcut("s")));
+
+            assertEquals(3, mManager.getRemainingCallCount()); // Still 3!
+        });
+    }
+
+
+    public void testThrottling_resetByInternalCall() throws Exception {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        // First, all packages have less than 3 (== initial value) remaining calls.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        // Simulate a call from sys UI.
+        mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING);
+        mManager.onApplicationActive(CALLING_PACKAGE_1, USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mManager.onApplicationActive(CALLING_PACKAGE_3, USER_0);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+
+        mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertEquals(3, mManager.getRemainingCallCount());
+        });
+    }
+
+    public void testReportShortcutUsed() {
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            reset(mMockUsageStatsManagerInternal);
+
+            // Report with an nonexistent shortcut.
+            mManager.reportShortcutUsed("s1");
+            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                    anyString(), anyString(), anyInt());
+
+            // Publish s2, but s1 still doesn't exist.
+            mManager.setDynamicShortcuts(list(makeShortcut("s2")));
+            mManager.reportShortcutUsed("s1");
+            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                    anyString(), anyString(), anyInt());
+
+            mManager.reportShortcutUsed("s2");
+            verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                    eq(CALLING_PACKAGE_1), eq("s2"), eq(USER_10));
+
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+            // Try with a different package.
+            reset(mMockUsageStatsManagerInternal);
+
+            // Report with an nonexistent shortcut.
+            mManager.reportShortcutUsed("s2");
+            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                    anyString(), anyString(), anyInt());
+
+            // Publish s2, but s1 still doesn't exist.
+            mManager.setDynamicShortcuts(list(makeShortcut("s3")));
+            mManager.reportShortcutUsed("s2");
+            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                    anyString(), anyString(), anyInt());
+
+            mManager.reportShortcutUsed("s3");
+            verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                    eq(CALLING_PACKAGE_2), eq("s3"), eq(USER_10));
+
+        });
+    }
+
+    // Test for a ShortcutInfo method.
+    public void testGetResourcePackageName() {
+        assertEquals(null, ShortcutInfo.getResourcePackageName(""));
+        assertEquals(null, ShortcutInfo.getResourcePackageName("abc"));
+        assertEquals("p", ShortcutInfo.getResourcePackageName("p:"));
+        assertEquals("p", ShortcutInfo.getResourcePackageName("p:xx"));
+        assertEquals("pac", ShortcutInfo.getResourcePackageName("pac:"));
+    }
+
+    // Test for a ShortcutInfo method.
+    public void testGetResourceTypeName() {
+        assertEquals(null, ShortcutInfo.getResourceTypeName(""));
+        assertEquals(null, ShortcutInfo.getResourceTypeName(":"));
+        assertEquals(null, ShortcutInfo.getResourceTypeName("/"));
+        assertEquals(null, ShortcutInfo.getResourceTypeName("/:"));
+        assertEquals("a", ShortcutInfo.getResourceTypeName(":a/"));
+        assertEquals("type", ShortcutInfo.getResourceTypeName("xxx:type/yyy"));
+    }
+
+    // Test for a ShortcutInfo method.
+    public void testGetResourceTypeAndEntryName() {
+        assertEquals(null, ShortcutInfo.getResourceTypeAndEntryName(""));
+        assertEquals(null, ShortcutInfo.getResourceTypeAndEntryName("abc"));
+        assertEquals("", ShortcutInfo.getResourceTypeAndEntryName("p:"));
+        assertEquals("x", ShortcutInfo.getResourceTypeAndEntryName(":x"));
+        assertEquals("x", ShortcutInfo.getResourceTypeAndEntryName("p:x"));
+        assertEquals("xyz", ShortcutInfo.getResourceTypeAndEntryName("pac:xyz"));
+    }
+
+    // Test for a ShortcutInfo method.
+    public void testGetResourceEntryName() {
+        assertEquals(null, ShortcutInfo.getResourceEntryName(""));
+        assertEquals(null, ShortcutInfo.getResourceEntryName("ab:"));
+        assertEquals("", ShortcutInfo.getResourceEntryName("/"));
+        assertEquals("abc", ShortcutInfo.getResourceEntryName("/abc"));
+        assertEquals("abc", ShortcutInfo.getResourceEntryName("xyz/abc"));
+    }
+
+    // Test for a ShortcutInfo method.
+    public void testLookUpResourceName_systemResources() {
+        // For android system resources, lookUpResourceName will simply return the value as a
+        // string, regardless of "withType".
+        final Resources res = getTestContext().getResources();
+
+        assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res,
+                android.R.string.cancel, true, getTestContext().getPackageName()));
+        assertEquals("" + android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceName(res,
+                android.R.drawable.alert_dark_frame, true, getTestContext().getPackageName()));
+        assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res,
+                android.R.string.cancel, false, getTestContext().getPackageName()));
+    }
+
+    public void testLookUpResourceName_appResources() {
+        final Resources res = getTestContext().getResources();
+
+        assertEquals("shortcut_text1", ShortcutInfo.lookUpResourceName(res,
+                R.string.shortcut_text1, false, getTestContext().getPackageName()));
+        assertEquals("string/shortcut_text1", ShortcutInfo.lookUpResourceName(res,
+                R.string.shortcut_text1, true, getTestContext().getPackageName()));
+
+        assertEquals("black_16x64", ShortcutInfo.lookUpResourceName(res,
+                R.drawable.black_16x64, false, getTestContext().getPackageName()));
+        assertEquals("drawable/black_16x64", ShortcutInfo.lookUpResourceName(res,
+                R.drawable.black_16x64, true, getTestContext().getPackageName()));
+    }
+
+    // Test for a ShortcutInfo method.
+    public void testLookUpResourceId_systemResources() {
+        final Resources res = getTestContext().getResources();
+
+        assertEquals(android.R.string.cancel, ShortcutInfo.lookUpResourceId(res,
+                "" + android.R.string.cancel, null,
+                getTestContext().getPackageName()));
+        assertEquals(android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceId(res,
+                "" + android.R.drawable.alert_dark_frame, null,
+                getTestContext().getPackageName()));
+    }
+
+    // Test for a ShortcutInfo method.
+    public void testLookUpResourceId_appResources() {
+        final Resources res = getTestContext().getResources();
+
+        assertEquals(R.string.shortcut_text1,
+                ShortcutInfo.lookUpResourceId(res, "shortcut_text1", "string",
+                        getTestContext().getPackageName()));
+
+        assertEquals(R.string.shortcut_text1,
+                ShortcutInfo.lookUpResourceId(res, "string/shortcut_text1", null,
+                        getTestContext().getPackageName()));
+
+        assertEquals(R.drawable.black_16x64,
+                ShortcutInfo.lookUpResourceId(res, "black_16x64", "drawable",
+                        getTestContext().getPackageName()));
+
+        assertEquals(R.drawable.black_16x64,
+                ShortcutInfo.lookUpResourceId(res, "drawable/black_16x64", null,
+                        getTestContext().getPackageName()));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
new file mode 100644
index 0000000..eb4db7a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -0,0 +1,505 @@
+/*
+ * 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 static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.ComponentName;
+import android.content.pm.ShortcutInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.frameworks.servicestests.R;
+import com.android.server.pm.ShortcutService.ConfigConstants;
+
+/**
+ * Tests related to shortcut rank auto-adjustment.
+ */
+@SmallTest
+public class ShortcutManagerTest3 extends BaseShortcutManagerTest {
+
+    private static final String CALLING_PACKAGE = CALLING_PACKAGE_1;
+
+    private static final ComponentName A1 = new ComponentName(CALLING_PACKAGE,
+            ShortcutActivity.class.getName());
+
+    private static final ComponentName A2 = new ComponentName(CALLING_PACKAGE,
+            ShortcutActivity2.class.getName());
+
+    private static final ComponentName A3 = new ComponentName(CALLING_PACKAGE,
+            ShortcutActivity3.class.getName());
+
+    private ShortcutInfo shortcut(String id, ComponentName activity, int rank) {
+        return makeShortcutWithActivityAndRank(id, activity, rank);
+    }
+
+    private ShortcutInfo shortcut(String id, ComponentName activity) {
+        return makeShortcutWithActivityAndRank(id, activity, ShortcutInfo.RANK_NOT_SET);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // We don't need throttling during this test class, and also relax the max cap.
+        mService.updateConfigurationLocked(
+                ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
+                + ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
+        );
+
+        setCaller(CALLING_PACKAGE, USER_0);
+    }
+
+    private void publishManifestShortcuts(ComponentName activity, int resId) {
+        addManifestShortcutResource(activity, resId);
+        updatePackageVersion(CALLING_PACKAGE, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE, USER_0));
+    }
+
+    public void testSetDynamicShortcuts_noManifestShortcuts() {
+        mManager.setDynamicShortcuts(list(
+                shortcut("s1", A1)
+        ));
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s1");
+
+        assertTrue(mManager.setDynamicShortcuts(list(
+                shortcut("s5", A1),
+                shortcut("s4", A1),
+                shortcut("s3", A1)
+        )));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s4", "s3");
+
+        // RANK_NOT_SET is always the last.
+        assertTrue(mManager.setDynamicShortcuts(list(
+                shortcut("s5", A1),
+                shortcut("s4", A1, 5),
+                shortcut("s3", A1, 3),
+                shortcut("s2", A1)
+        )));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s3", "s4", "s5", "s2");
+
+        // Same rank, preserve the argument order.
+        assertTrue(mManager.setDynamicShortcuts(list(
+                shortcut("s5", A1, 5),
+                shortcut("s4", A1, 0),
+                shortcut("s3", A1, 5)
+        )));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s4", "s5", "s3");
+
+        // Multiple activities.
+        assertTrue(mManager.setDynamicShortcuts(list(
+                shortcut("s5", A1),
+                shortcut("s4", A2),
+                shortcut("s3", A3)
+        )));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5");
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("s4");
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A3)
+                .haveRanksInOrder("s3");
+
+        assertTrue(mManager.setDynamicShortcuts(list(
+                shortcut("s5", A1, 5),
+                shortcut("s4", A1),
+                shortcut("s3", A1, 5),
+                shortcut("x5", A2, 5),
+                shortcut("x4", A2),
+                shortcut("x3", A2, 1)
+        )));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s4");
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x5", "x4");
+
+        // Clear.  Make sure it wouldn't lead to invalid internals state.
+        // (ShortcutService.verifyStates() will do so internally.)
+        assertTrue(mManager.setDynamicShortcuts(list()));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1).isEmpty();
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2).isEmpty();
+    }
+
+    private void runTestWithManifestShortcuts(Runnable r) {
+        publishManifestShortcuts(A1, R.xml.shortcut_5_alt);
+        publishManifestShortcuts(A2, R.xml.shortcut_1);
+
+        assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A1)
+                .haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+
+        assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A2)
+                .haveRanksInOrder("ms1");
+
+        // Existence of manifest shortcuts shouldn't affect dynamic shortcut ranks,
+        // so running another test here should pass.
+        r.run();
+
+        // And dynamic shortcut tests shouldn't affect manifest shortcuts, so repeat the
+        // same check.
+        assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A1)
+                .haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+
+        assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A2)
+                .haveRanksInOrder("ms1");
+    }
+
+    public void testSetDynamicShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
+    }
+
+    public void testAddDynamicShortcuts_noManifestShortcuts() {
+        mManager.addDynamicShortcuts(list(
+                shortcut("s1", A1)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s1");
+
+        //------------------------------------------------------
+        long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.addDynamicShortcuts(list(
+                shortcut("s5", A1, 0),
+                shortcut("s4", A1),
+                shortcut("s2", A1, 3),
+                shortcut("x1", A2),
+                shortcut("x3", A2, 2),
+                shortcut("x2", A2, 2),
+                shortcut("s3", A1, 0)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s1", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s5", "s3", "s1", "s2", "s4", "x3", "x2", "x1");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.addDynamicShortcuts(list(
+                shortcut("s1", A1, 1)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s1", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s1", "s3");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.addDynamicShortcuts(list(
+                shortcut("s1", A1, 1),
+
+                // This is add, not update, so the following means s5 will have NO_RANK,
+                // which puts it at the end.
+                shortcut("s5", A1),
+                shortcut("s3", A1, 0),
+
+                // s10 also has NO_RANK, so it'll be put at the end, even after "s5" as we preserve
+                // the argument order.
+                shortcut("s10", A1),
+
+                // Note we're changing the activity for x2.
+                shortcut("x2", A1, 0),
+                shortcut("x10", A2)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s3", "x2", "s1", "s2", "s4", "s5", "s10");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x1", "x10");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s3", "x2", "s1", "s5", "s10", "x1", "x10");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        // Change the activities again.
+        mManager.addDynamicShortcuts(list(
+                shortcut("s1", A2),
+                shortcut("s2", A2, 999)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s3", "x2", "s4", "s5", "s10");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x1", "x10", "s2", "s1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s1", "s2", "s4", "s5", "s10");
+    }
+
+    public void testAddDynamicShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
+    }
+
+    public void testUpdateShortcuts_noManifestShortcuts() {
+        mManager.addDynamicShortcuts(list(
+                shortcut("s5", A1, 0),
+                shortcut("s4", A1),
+                shortcut("s2", A1, 3),
+                shortcut("x1", A2),
+                shortcut("x3", A2, 2),
+                shortcut("x2", A2, 2),
+                shortcut("s3", A1, 0)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        //------------------------------------------------------
+        long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.updateShortcuts(list());
+        // Same order.
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .isEmpty();
+
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE, list("s2", "s4", "x2"), HANDLE_USER_0);
+        });
+        // Still same order.
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.updateShortcuts(list(
+                shortcut("s4", A1, 1),
+
+                // Rank not changing, should keep the same positions.
+                // c.f. in case of addDynamicShortcuts, this means "put them at the end".
+                shortcut("s3", A1),
+                shortcut("x2", A2)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s4", "s3", "s2");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s4", "s3", "s2", "x2");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.updateShortcuts(list(
+                shortcut("s4", A1, 0),
+
+                // Change the activity without specifying a rank -> keep the same rank.
+                shortcut("s5", A2),
+
+                // Change the activity without specifying a rank -> assign a new rank.
+                shortcut("x2", A1, 2),
+
+                // "xx" doesn't exist, so it'll be ignored.
+                shortcut("xx", A1, 0)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s4", "x2", "s3", "s2");
+
+        // Interesting case: both x3 and s5 originally had rank=0, and in this case s5 has moved
+        // to A2 without changing the rank.  So they're tie for the new rank, as well as
+        // the "rank changed" bit.  Also in this case, "s5" won't have an implicit order, since
+        // its rank isn't changing.  So we sort them by ID, thus s5 comes before x3.
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("s5", "x3", "x1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s4", "x2", "s5", "x3");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.updateShortcuts(list(
+                shortcut("s3", A3)));
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s4", "x2", "s2");
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("s5", "x3", "x1");
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A3)
+                .haveRanksInOrder("s3");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s3", "s2");
+    }
+
+    public void testUpdateShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() -> testUpdateShortcuts_noManifestShortcuts());
+    }
+
+    public void testDeleteDynamicShortcuts_noManifestShortcuts() {
+        mManager.addDynamicShortcuts(list(
+                shortcut("s5", A1, 0),
+                shortcut("s4", A1),
+                shortcut("s2", A1, 3),
+                shortcut("x1", A2),
+                shortcut("x3", A2, 2),
+                shortcut("x2", A2, 2),
+                shortcut("s3", A1, 0)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        //------------------------------------------------------
+        long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.removeDynamicShortcuts(list());
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .isEmpty();
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(
+                    CALLING_PACKAGE, list("s2", "s4", "x1", "x2"), HANDLE_USER_0);
+        });
+        // Still same order.
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.removeDynamicShortcuts(list("s3", "x1", "xxxx"));
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s2", "s4");
+    }
+
+    public void testDeleteDynamicShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() -> testDeleteDynamicShortcuts_noManifestShortcuts());
+    }
+
+    public void testDisableShortcuts_noManifestShortcuts() {
+        mManager.addDynamicShortcuts(list(
+                shortcut("s5", A1, 0),
+                shortcut("s4", A1),
+                shortcut("s2", A1, 3),
+                shortcut("x1", A2),
+                shortcut("x3", A2, 2),
+                shortcut("x2", A2, 2),
+                shortcut("s3", A1, 0)
+        ));
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        //------------------------------------------------------
+        long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.disableShortcuts(list());
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2", "x1");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .isEmpty();
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.disableShortcuts(list("s3", "x1", "xxxx"));
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s2", "s4");
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE, list("s2", "s4", "x2"), HANDLE_USER_0);
+        });
+        // Still same order.
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s2", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x3", "x2");
+
+        //------------------------------------------------------
+        lastApiTime = ++mInjectedCurrentTimeMillis;
+
+        mManager.disableShortcuts(list("s2", "x3"));
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+                .haveRanksInOrder("s5", "s4");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+                .haveRanksInOrder("x2");
+
+        assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+                .haveIds("s4", "x2");
+    }
+
+    public void testDisableShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() -> testDisableShortcuts_noManifestShortcuts());
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 48a11c5..9f77297 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -23,12 +23,14 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
 
+@SmallTest
 public class UserManagerServiceTest extends AndroidTestCase {
     private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["};
     private File restrictionsFile;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 6c2fcd5..ced4980 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -25,6 +25,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -33,6 +34,7 @@
 import java.util.List;
 
 /** Test {@link UserManager} functionality. */
+@MediumTest
 public class UserManagerTest extends AndroidTestCase {
     private static final int REMOVE_CHECK_INTERVAL = 500;
     private static final int REMOVE_TIMEOUT = 60 * 1000;
@@ -96,7 +98,7 @@
                     && !user.isPrimary()) {
                 found = true;
                 Bundle restrictions = mUserManager.getUserRestrictions(user.getUserHandle());
-                assertFalse("New user should have DISALLOW_CONFIG_WIFI =false by default",
+                assertTrue("Guest user should have DISALLOW_CONFIG_WIFI=true by default",
                         restrictions.getBoolean(UserManager.DISALLOW_CONFIG_WIFI));
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index 5bdf6f7..e2dce853 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -23,6 +23,7 @@
 import android.os.UserManager;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
 
 /**
  * Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
@@ -35,6 +36,7 @@
      -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
  * </pre>
  */
+@SmallTest
 public class UserRestrictionsUtilsTest extends AndroidTestCase {
     public void testNonNull() {
         Bundle out = UserRestrictionsUtils.nonNull(null);
diff --git a/services/tests/shortcutmanagerutils/Android.mk b/services/tests/shortcutmanagerutils/Android.mk
new file mode 100644
index 0000000..701e058
--- /dev/null
+++ b/services/tests/shortcutmanagerutils/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    mockito-target
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
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
new file mode 100644
index 0000000..7ba4c68
--- /dev/null
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -0,0 +1,721 @@
+/*
+ * 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.shortcutmanagertest;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.test.MoreAsserts;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.mockito.Mockito;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Common utility methods for ShortcutManager tests.  This is used by both CTS and the unit tests.
+ * Because it's used by CTS too, it can only access the public APIs.
+ */
+public class ShortcutManagerTestUtils {
+    private static final String TAG = "ShortcutManagerUtils";
+
+    private static final boolean ENABLE_DUMPSYS = false; // DO NOT SUBMIT WITH true
+
+    private static final int STANDARD_TIMEOUT_SEC = 5;
+
+    private static final String[] EMPTY_STRINGS = new String[0];
+
+    private ShortcutManagerTestUtils() {
+    }
+
+    private static List<String> readAll(ParcelFileDescriptor pfd) {
+        try {
+            try {
+                final ArrayList<String> ret = new ArrayList<>();
+                try (BufferedReader r = new BufferedReader(
+                        new FileReader(pfd.getFileDescriptor()))) {
+                    String line;
+                    while ((line = r.readLine()) != null) {
+                        ret.add(line);
+                    }
+                    r.readLine();
+                }
+                return ret;
+            } finally {
+                pfd.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static String concatResult(List<String> result) {
+        final StringBuilder sb = new StringBuilder();
+        for (String s : result) {
+            sb.append(s);
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+
+    private static List<String> runCommand(Instrumentation instrumentation, String command) {
+        return runCommand(instrumentation, command, null);
+    }
+    private static List<String> runCommand(Instrumentation instrumentation, String command,
+            Predicate<List<String>> resultAsserter) {
+        Log.d(TAG, "Running command: " + command);
+        final List<String> result;
+        try {
+            result = readAll(
+                    instrumentation.getUiAutomation().executeShellCommand(command));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        if (resultAsserter != null && !resultAsserter.test(result)) {
+            fail("Command '" + command + "' failed, output was:\n" + concatResult(result));
+        }
+        return result;
+    }
+
+    private static void runCommandForNoOutput(Instrumentation instrumentation, String command) {
+        runCommand(instrumentation, command, result -> result.size() == 0);
+    }
+
+    private static List<String> runShortcutCommand(Instrumentation instrumentation, String command,
+            Predicate<List<String>> resultAsserter) {
+        return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter);
+    }
+
+    public static List<String> runShortcutCommandForSuccess(Instrumentation instrumentation,
+            String command) {
+        return runShortcutCommand(instrumentation, command, result -> result.contains("Success"));
+    }
+
+    public static String getDefaultLauncher(Instrumentation instrumentation) {
+        final String PREFIX = "Launcher: ComponentInfo{";
+        final String POSTFIX = "}";
+        final List<String> result = runShortcutCommandForSuccess(
+                instrumentation, "get-default-launcher");
+        for (String s : result) {
+            if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
+                return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
+            }
+        }
+        fail("Default launcher not found");
+        return null;
+    }
+
+    public static void setDefaultLauncher(Instrumentation instrumentation, String component) {
+        runCommandForNoOutput(instrumentation, "cmd package set-home-activity " + component);
+    }
+
+    public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
+        setDefaultLauncher(instrumentation, packageContext.getPackageName()
+                + "/android.content.pm.cts.shortcutmanager.packages.Launcher");
+    }
+
+    public static void overrideConfig(Instrumentation instrumentation, String config) {
+        runShortcutCommandForSuccess(instrumentation, "override-config " + config);
+    }
+
+    public static void resetConfig(Instrumentation instrumentation) {
+        runShortcutCommandForSuccess(instrumentation, "reset-config");
+    }
+
+    public static void resetThrottling(Instrumentation instrumentation) {
+        runShortcutCommandForSuccess(instrumentation, "reset-throttling");
+    }
+
+    public static void resetAllThrottling(Instrumentation instrumentation) {
+        runShortcutCommandForSuccess(instrumentation, "reset-all-throttling");
+    }
+
+    public static void clearShortcuts(Instrumentation instrumentation, int userId,
+            String packageName) {
+        runShortcutCommandForSuccess(instrumentation, "clear-shortcuts "
+                + " --user " + userId + " " + packageName);
+    }
+
+    public static void dumpsysShortcut(Instrumentation instrumentation) {
+        if (!ENABLE_DUMPSYS) {
+            return;
+        }
+        for (String s : runCommand(instrumentation, "dumpsys shortcut")) {
+            Log.e(TAG, s);
+        }
+    }
+
+    public static Bundle makeBundle(Object... keysAndValues) {
+        assertTrue((keysAndValues.length % 2) == 0);
+
+        if (keysAndValues.length == 0) {
+            return null;
+        }
+        final Bundle ret = new Bundle();
+
+        for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
+            final String key = keysAndValues[i].toString();
+            final Object value = keysAndValues[i + 1];
+
+            if (value == null) {
+                ret.putString(key, null);
+            } else if (value instanceof Integer) {
+                ret.putInt(key, (Integer) value);
+            } else if (value instanceof String) {
+                ret.putString(key, (String) value);
+            } else if (value instanceof Bundle) {
+                ret.putBundle(key, (Bundle) value);
+            } else {
+                fail("Type not supported yet: " + value.getClass().getName());
+            }
+        }
+        return ret;
+    }
+
+    public static <T> List<T> list(T... array) {
+        return Arrays.asList(array);
+    }
+
+    public static <T> Set<T> hashSet(Set<T> in) {
+        return new LinkedHashSet<>(in);
+    }
+
+    public static <T> Set<T> set(T... values) {
+        return set(v -> v, values);
+    }
+
+    public static <T, V> Set<T> set(Function<V, T> converter, V... values) {
+        return set(converter, Arrays.asList(values));
+    }
+
+    public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) {
+        final LinkedHashSet<T> ret = new LinkedHashSet<>();
+        for (V v : values) {
+            ret.add(converter.apply(v));
+        }
+        return ret;
+    }
+
+    public static void resetAll(Collection<?> mocks) {
+        for (Object o : mocks) {
+            reset(o);
+        }
+    }
+
+    public static <T> List<T> assertEmpty(List<T> list) {
+        assertEquals(0, list.size());
+        return list;
+    }
+
+    public static List<ShortcutInfo> filter(List<ShortcutInfo> list, Predicate<ShortcutInfo> p) {
+        final ArrayList<ShortcutInfo> ret = new ArrayList<>(list);
+        ret.removeIf(si -> !p.test(si));
+        return ret;
+    }
+
+    public static List<ShortcutInfo> filterByActivity(List<ShortcutInfo> list,
+            ComponentName activity) {
+        return filter(list, si ->
+                (si.getActivity().equals(activity)
+                        && (si.isManifestShortcut() || si.isDynamic())));
+    }
+
+    public static List<ShortcutInfo> changedSince(List<ShortcutInfo> list, long time) {
+        return filter(list, si -> si.getLastChangedTimestamp() >= time);
+    }
+
+    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);
+    }
+
+    public static void assertExpectException(String message,
+            Class<? extends Throwable> expectedExceptionType,
+            String expectedExceptionMessageRegex, Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable e) {
+            Assert.assertTrue(
+                    "Expected exception type was " + expectedExceptionType.getName()
+                            + " but caught " + e + " (message=" + message + ")",
+                    expectedExceptionType.isAssignableFrom(e.getClass()));
+            if (expectedExceptionMessageRegex != null) {
+                MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
+            }
+            return; // Pass
+        }
+        Assert.fail("Expected exception type " + expectedExceptionType.getName()
+                + " was not thrown");
+    }
+
+    public static List<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts,
+            String... expectedIds) {
+        final SortedSet<String> expected = new TreeSet<>(list(expectedIds));
+        final SortedSet<String> actual = new TreeSet<>();
+        for (ShortcutInfo s : actualShortcuts) {
+            actual.add(s.getId());
+        }
+
+        // Compare the sets.
+        assertEquals(expected, actual);
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertShortcutIdsOrdered(List<ShortcutInfo> actualShortcuts,
+            String... expectedIds) {
+        final ArrayList<String> expected = new ArrayList<>(list(expectedIds));
+        final ArrayList<String> actual = new ArrayList<>();
+        for (ShortcutInfo s : actualShortcuts) {
+            actual.add(s.getId());
+        }
+        assertEquals(expected, actual);
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllHaveIntents(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNotNull("ID " + s.getId(), s.getIntent());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllNotHaveIntents(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNull("ID " + s.getId(), s.getIntent());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllHaveTitle(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNotNull("ID " + s.getId(), s.getShortLabel());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllNotHaveTitle(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNull("ID " + s.getId(), s.getShortLabel());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllHaveIconResId(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource());
+            assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllHaveIconFile(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource());
+            assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllHaveIcon(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllKeyFieldsOnly(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllNotKeyFieldsOnly(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllDynamic(List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.isDynamic());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllPinned(List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.isPinned());
+        }
+        return actualShortcuts;
+    }
+
+    public static List<ShortcutInfo> assertAllDynamicOrPinned(
+            List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned());
+        }
+        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) {
+            assertTrue("ID " + s.getId(), s.hasStringResourcesResolved());
+        }
+        return actualShortcuts;
+    }
+
+    public static void assertDynamicOnly(ShortcutInfo si) {
+        assertTrue(si.isDynamic());
+        assertFalse(si.isPinned());
+    }
+
+    public static void assertPinnedOnly(ShortcutInfo si) {
+        assertFalse(si.isDynamic());
+        assertFalse(si.isManifestShortcut());
+        assertTrue(si.isPinned());
+    }
+
+    public static void assertDynamicAndPinned(ShortcutInfo si) {
+        assertTrue(si.isDynamic());
+        assertTrue(si.isPinned());
+    }
+
+    public static void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
+        assertEquals("width", expectedWidth, bitmap.getWidth());
+        assertEquals("height", expectedHeight, bitmap.getHeight());
+    }
+
+    public static <T> void assertAllUnique(Collection<T> list) {
+        final Set<Object> set = new LinkedHashSet<>();
+        for (T item : list) {
+            if (set.contains(item)) {
+                fail("Duplicate item found: " + item + " (in the list: " + list + ")");
+            }
+            set.add(item);
+        }
+    }
+
+    public static ShortcutInfo findShortcut(List<ShortcutInfo> list, String id) {
+        for (ShortcutInfo si : list) {
+            if (si.getId().equals(id)) {
+                return si;
+            }
+        }
+        fail("Shortcut " + id + " not found in the list");
+        return null;
+    }
+
+    public static Bitmap pfdToBitmap(ParcelFileDescriptor pfd) {
+        assertNotNull(pfd);
+        try {
+            try {
+                return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
+            } finally {
+                pfd.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void assertBundleEmpty(BaseBundle b) {
+        assertTrue(b == null || b.size() == 0);
+    }
+
+    public static void assertCallbackNotReceived(LauncherApps.Callback mock) {
+        verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(),
+                any(UserHandle.class));
+    }
+
+    public static void assertCallbackReceived(LauncherApps.Callback mock,
+            UserHandle user, String packageName, String... ids) {
+        verify(mock).onShortcutsChanged(eq(packageName), checkShortcutIds(ids),
+                eq(user));
+    }
+
+    public static boolean checkAssertSuccess(Runnable r) {
+        try {
+            r.run();
+            return true;
+        } catch (AssertionError e) {
+            return false;
+        }
+    }
+
+    public static <T> T checkArgument(Predicate<T> checker, String description,
+            List<T> matchedCaptor) {
+        final Matcher<T> m = new BaseMatcher<T>() {
+            @Override
+            public boolean matches(Object item) {
+                if (item == null) {
+                    return false;
+                }
+                final T value = (T) item;
+                if (!checker.test(value)) {
+                    return false;
+                }
+
+                if (matchedCaptor != null) {
+                    matchedCaptor.add(value);
+                }
+                return true;
+            }
+
+            @Override
+            public void describeTo(Description d) {
+                d.appendText(description);
+            }
+        };
+        return Mockito.argThat(m);
+    }
+
+    public static List<ShortcutInfo> checkShortcutIds(String... ids) {
+        return checkArgument((List<ShortcutInfo> list) -> {
+            final Set<String> actualSet = set(si -> si.getId(), list);
+            return actualSet.equals(set(ids));
+
+        }, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null);
+    }
+
+    public static ShortcutInfo parceled(ShortcutInfo si) {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(si, 0);
+        p.setDataPosition(0);
+        ShortcutInfo si2 = p.readParcelable(ShortcutManagerTestUtils.class.getClassLoader());
+        p.recycle();
+        return si2;
+    }
+
+    public static List<ShortcutInfo> cloneShortcutList(List<ShortcutInfo> list) {
+        if (list == null) {
+            return null;
+        }
+        final List<ShortcutInfo> ret = new ArrayList<>(list.size());
+        for (ShortcutInfo si : list) {
+            ret.add(parceled(si));
+        }
+
+        return ret;
+    }
+
+    private static final Comparator<ShortcutInfo> sRankComparator =
+            (ShortcutInfo a, ShortcutInfo b) -> Integer.compare(a.getRank(), b.getRank());
+
+    public static List<ShortcutInfo> sortedByRank(List<ShortcutInfo> shortcuts) {
+        final ArrayList<ShortcutInfo> ret = new ArrayList<>(shortcuts);
+        Collections.sort(ret, sRankComparator);
+        return ret;
+    }
+
+    public static void waitUntil(String message, BooleanSupplier condition) {
+        waitUntil(message, condition, STANDARD_TIMEOUT_SEC);
+    }
+
+    public static void waitUntil(String message, BooleanSupplier condition, int timeoutSeconds) {
+        final long timeout = System.currentTimeMillis() + (timeoutSeconds * 1000L);
+        while (System.currentTimeMillis() < timeout) {
+            if (condition.getAsBoolean()) {
+                return;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        fail("Timed out for: " + message);
+    }
+
+    public static ShortcutListAsserter assertWith(List<ShortcutInfo> list) {
+        return new ShortcutListAsserter(list);
+    }
+
+    /**
+     * New style assertion that allows chained calls.
+     */
+    public static class ShortcutListAsserter {
+        private final List<ShortcutInfo> mList;
+
+        ShortcutListAsserter(List<ShortcutInfo> list) {
+            mList = new ArrayList<>(list);
+        }
+
+        public ShortcutListAsserter selectDynamic() {
+            return new ShortcutListAsserter(
+                    filter(mList, ShortcutInfo::isDynamic));
+        }
+
+        public ShortcutListAsserter selectManifest() {
+            return new ShortcutListAsserter(
+                    filter(mList, ShortcutInfo::isManifestShortcut));
+        }
+
+        public ShortcutListAsserter selectPinned() {
+            return new ShortcutListAsserter(
+                    filter(mList, ShortcutInfo::isPinned));
+        }
+
+        public ShortcutListAsserter selectByActivity(ComponentName activity) {
+            return new ShortcutListAsserter(
+                    ShortcutManagerTestUtils.filterByActivity(mList, activity));
+        }
+
+        public ShortcutListAsserter selectByChangedSince(long time) {
+            return new ShortcutListAsserter(
+                    ShortcutManagerTestUtils.changedSince(mList, time));
+        }
+
+        public ShortcutListAsserter toSortByRank() {
+            return new ShortcutListAsserter(
+                    ShortcutManagerTestUtils.sortedByRank(mList));
+        }
+
+        public ShortcutListAsserter haveIds(String... expectedIds) {
+            assertShortcutIds(mList, expectedIds);
+            return this;
+        }
+
+        public ShortcutListAsserter haveIdsOrdered(String... expectedIds) {
+            assertShortcutIdsOrdered(mList, expectedIds);
+            return this;
+        }
+
+        private ShortcutListAsserter haveSequentialRanks() {
+            for (int i = 0; i < mList.size(); i++) {
+                assertEquals("Rank not sequential", i, mList.get(i).getRank());
+            }
+            return this;
+        }
+
+        public ShortcutListAsserter haveRanksInOrder(String... expectedIds) {
+            toSortByRank()
+                    .haveSequentialRanks()
+                    .haveIdsOrdered(expectedIds);
+            return this;
+        }
+
+        public ShortcutListAsserter isEmpty() {
+            assertEquals(0, mList.size());
+            return this;
+        }
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ecfeff9..eb3c665 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1412,6 +1412,24 @@
         }
 
         @Override
+        public void reportShortcutUsage(String packageName, String shortcutId, int userId) {
+            if (packageName == null || shortcutId == null) {
+                Slog.w(TAG, "Event reported without a package name or a shortcut ID");
+                return;
+            }
+
+            UsageEvents.Event event = new UsageEvents.Event();
+            event.mPackage = packageName.intern();
+            event.mShortcutId = shortcutId.intern();
+
+            // This will later be converted to system time.
+            event.mTimeStamp = SystemClock.elapsedRealtime();
+
+            event.mEventType = Event.SHORTCUT_INVOCATION;
+            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+        }
+
+        @Override
         public void reportContentProviderUsage(String name, String packageName, int userId) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = name;
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index c95ff23..03cee9c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -51,6 +51,7 @@
     private static final String ACTIVE_ATTR = "active";
     private static final String LAST_EVENT_ATTR = "lastEvent";
     private static final String TYPE_ATTR = "type";
+    private static final String SHORTCUT_ID_ATTR = "shortcutId";
 
     // Time attributes stored as an offset of the beginTime.
     private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
@@ -106,9 +107,15 @@
         event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
 
         event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
-        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
-            event.mConfiguration = new Configuration();
-            Configuration.readXmlAttrs(parser, event.mConfiguration);
+        switch (event.mEventType) {
+            case UsageEvents.Event.CONFIGURATION_CHANGE:
+                event.mConfiguration = new Configuration();
+                Configuration.readXmlAttrs(parser, event.mConfiguration);
+                break;
+            case UsageEvents.Event.SHORTCUT_INVOCATION:
+                final String id = XmlUtils.readStringAttribute(parser, SHORTCUT_ID_ATTR);
+                event.mShortcutId = (id != null) ? id.intern() : null;
+                break;
         }
 
         if (statsOut.events == null) {
@@ -165,9 +172,17 @@
         }
         XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
 
-        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE
-                && event.mConfiguration != null) {
-            Configuration.writeXmlAttrs(xml, event.mConfiguration);
+        switch (event.mEventType) {
+            case UsageEvents.Event.CONFIGURATION_CHANGE:
+                if (event.mConfiguration != null) {
+                    Configuration.writeXmlAttrs(xml, event.mConfiguration);
+                }
+                break;
+            case UsageEvents.Event.SHORTCUT_INVOCATION:
+                if (event.mShortcutId != null) {
+                    XmlUtils.writeStringAttribute(xml, SHORTCUT_ID_ATTR, event.mShortcutId);
+                }
+                break;
         }
 
         xml.endTag(null, EVENT_TAG);
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 7d003f3..59e4c80 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -549,6 +549,9 @@
             if (event.mConfiguration != null) {
                 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
             }
+            if (event.mShortcutId != null) {
+                pw.printPair("shortcutId", event.mShortcutId);
+            }
             pw.println();
         }
         pw.decreaseIndent();
@@ -588,6 +591,8 @@
                 return "SYSTEM_INTERACTION";
             case UsageEvents.Event.USER_INTERACTION:
                 return "USER_INTERACTION";
+            case UsageEvents.Event.SHORTCUT_INVOCATION:
+                return "SHORTCUT_INVOCATION";
             default:
                 return "UNKNOWN";
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index 0f68cca..0dcd1521 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -40,11 +40,12 @@
     static final boolean DBG = false;
 
     private static final String NAME = "sound_model.db";
-    private static final int VERSION = 4;
+    private static final int VERSION = 5;
 
     public static interface SoundModelContract {
         public static final String TABLE = "sound_model";
         public static final String KEY_MODEL_UUID = "model_uuid";
+        public static final String KEY_VENDOR_UUID = "vendor_uuid";
         public static final String KEY_KEYPHRASE_ID = "keyphrase_id";
         public static final String KEY_TYPE = "type";
         public static final String KEY_DATA = "data";
@@ -58,6 +59,7 @@
     private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE "
             + SoundModelContract.TABLE + "("
             + SoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+            + SoundModelContract.KEY_VENDOR_UUID + " TEXT, "
             + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER,"
             + SoundModelContract.KEY_TYPE + " INTEGER,"
             + SoundModelContract.KEY_DATA + " BLOB,"
@@ -78,9 +80,19 @@
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        // TODO: For now, drop older tables and recreate new ones.
-        db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE);
-        onCreate(db);
+        if (oldVersion < 4) {
+            // For old versions just drop the tables and recreate new ones.
+            db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE);
+            onCreate(db);
+        } else {
+            // In the jump to version 5, we added support for the vendor UUID.
+            if (oldVersion == 4) {
+                Slog.d(TAG, "Adding vendor UUID column");
+                db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN "
+                        + SoundModelContract.KEY_VENDOR_UUID + " TEXT");
+                oldVersion++;
+            }
+        }
     }
 
     /**
@@ -93,6 +105,9 @@
             SQLiteDatabase db = getWritableDatabase();
             ContentValues values = new ContentValues();
             values.put(SoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString());
+            if (soundModel.vendorUuid != null) {
+                values.put(SoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString());
+            }
             values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE);
             values.put(SoundModelContract.KEY_DATA, soundModel.data);
 
@@ -176,6 +191,11 @@
                             continue;
                         }
 
+                        String vendorUuidString = null;
+                        int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID);
+                        if (vendorUuidColumn != -1) {
+                            vendorUuidString = c.getString(vendorUuidColumn);
+                        }
                         byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
                         int recognitionModes = c.getInt(
                                 c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
@@ -212,9 +232,12 @@
                         Keyphrase[] keyphrases = new Keyphrase[1];
                         keyphrases[0] = new Keyphrase(
                                 keyphraseId, recognitionModes, modelLocale, text, users);
+                        UUID vendorUuid = null;
+                        if (vendorUuidString != null) {
+                            vendorUuid = UUID.fromString(vendorUuidString);
+                        }
                         KeyphraseSoundModel model = new KeyphraseSoundModel(
-                                UUID.fromString(modelUuid),
-                                null /* FIXME use vendor UUID */, data, keyphrases);
+                                UUID.fromString(modelUuid), vendorUuid, data, keyphrases);
                         if (DBG) {
                             Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
                                     + model);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 7d7e1eb..3a6d1fd 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -23,6 +23,7 @@
 
 import java.lang.String;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -104,7 +105,6 @@
      * An {@link InCallService} will only see this state if it has the
      * {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true} in its
      * manifest.
-     * @hide
      */
     public static final int STATE_PULLING_CALL = 11;
 
@@ -252,7 +252,6 @@
          * <p>
          * See {@link Connection#CAPABILITY_CAN_PULL_CALL} and
          * {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
-         * @hide
          */
         public static final int CAPABILITY_CAN_PULL_CALL = 0x00800000;
 
@@ -305,10 +304,14 @@
          * in its manifest.
          * <p>
          * See {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
-         * @hide
          */
         public static final int PROPERTY_IS_EXTERNAL_CALL = 0x00000040;
 
+        /**
+         * Indicates that the call has CDMA Enhanced Voice Privacy enabled.
+         */
+        public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 0x00000080;
+
         //******************************************************************************************
         // Next PROPERTY value: 0x00000100
         //******************************************************************************************
@@ -465,6 +468,9 @@
             if (hasProperty(properties, PROPERTY_IS_EXTERNAL_CALL)) {
                 builder.append(" PROPERTY_IS_EXTERNAL_CALL");
             }
+            if(hasProperty(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
+                builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
+            }
             builder.append("]");
             return builder.toString();
         }
@@ -786,7 +792,6 @@
          * @param call The {@code Call} receiving the event.
          * @param event The event.
          * @param extras Extras associated with the connection event.
-         * @hide
          */
         public void onConnectionEvent(Call call, String event, Bundle extras) {}
     }
@@ -965,7 +970,6 @@
      * An {@link InCallService} will only see calls which support this method if it has the
      * {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true}
      * in its manifest.
-     * @hide
      */
     public void pullExternalCall() {
         // If this isn't an external call, ignore the request.
@@ -988,7 +992,6 @@
      *
      * @param event The connection event.
      * @param extras Bundle containing extra information associated with the event.
-     * @hide
      */
     public void sendCallEvent(String event, Bundle extras) {
         mInCallAdapter.sendCallEvent(mTelecomCallId, event, extras);
@@ -1002,7 +1005,6 @@
      * extras.  Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
      *
      * @param extras The extras to add.
-     * @hide
      */
     public final void putExtras(Bundle extras) {
         if (extras == null) {
@@ -1032,7 +1034,7 @@
     }
 
     /**
-     * Adds an integer extra to this {@code Connection}.
+     * Adds an integer extra to this {@link Call}.
      *
      * @param key The extra key.
      * @param value The value.
@@ -1047,7 +1049,7 @@
     }
 
     /**
-     * Adds a string extra to this {@code Connection}.
+     * Adds a string extra to this {@link Call}.
      *
      * @param key The extra key.
      * @param value The value.
@@ -1062,10 +1064,9 @@
     }
 
     /**
-     * Removes extras from this {@code Connection}.
+     * Removes extras from this {@link Call}.
      *
      * @param keys The keys of the extras to remove.
-     * @hide
      */
     public final void removeExtras(List<String> keys) {
         if (mExtras != null) {
@@ -1080,6 +1081,15 @@
     }
 
     /**
+     * Removes extras from this {@link Call}.
+     *
+     * @param keys The keys of the extras to remove.
+     */
+    public final void removeExtras(String ... keys) {
+        removeExtras(Arrays.asList(keys));
+    }
+
+    /**
      * Obtains the parent of this {@code Call} in a conference, if any.
      *
      * @return The parent {@code Call}, or {@code null} if this {@code Call} is not a
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 0227d27..0eabf15 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -24,6 +24,7 @@
 import android.util.ArraySet;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -164,7 +165,6 @@
      * {@link Connection} for valid values.
      *
      * @return A bitmask of the properties of the conference call.
-     * @hide
      */
     public final int getConnectionProperties() {
         return mConnectionProperties;
@@ -396,7 +396,6 @@
      * {@link Connection} for valid values.
      *
      * @param connectionProperties A bitmask of the {@code Properties} of the conference call.
-     * @hide
      */
     public final void setConnectionProperties(int connectionProperties) {
         if (connectionProperties != mConnectionProperties) {
@@ -685,6 +684,8 @@
      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
      *
      * @param extras The extras associated with this {@code Conference}.
+     * @deprecated Use {@link #putExtras(Bundle)} to add extras.  Use {@link #removeExtras(List)}
+     * to remove extras.
      */
     public final void setExtras(@Nullable Bundle extras) {
         // Keeping putExtras and removeExtras in the same lock so that this operation happens as a
@@ -727,7 +728,6 @@
      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
      *
      * @param extras The extras to add.
-     * @hide
      */
     public final void putExtras(@NonNull Bundle extras) {
         if (extras == null) {
@@ -790,10 +790,9 @@
     }
 
     /**
-     * Removes an extra from this {@link Conference}.
+     * Removes extras from this {@link Conference}.
      *
-     * @param keys The key of the extra key to remove.
-     * @hide
+     * @param keys The keys of the extras to remove.
      */
     public final void removeExtras(List<String> keys) {
         if (keys == null || keys.isEmpty()) {
@@ -815,7 +814,25 @@
     }
 
     /**
+     * Removes extras from this {@link Conference}.
+     *
+     * @param keys The keys of the extras to remove.
+     */
+    public final void removeExtras(String ... keys) {
+        removeExtras(Arrays.asList(keys));
+    }
+
+    /**
      * Returns the extras associated with this conference.
+     * <p>
+     * Extras should be updated using {@link #putExtras(Bundle)} and {@link #removeExtras(List)}.
+     * <p>
+     * Telecom or an {@link InCallService} can also update the extras via
+     * {@link android.telecom.Call#putExtras(Bundle)}, and
+     * {@link Call#removeExtras(List)}.
+     * <p>
+     * The conference is notified of changes to the extras made by Telecom or an
+     * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
      *
      * @return The extras associated with this connection.
      */
@@ -832,7 +849,6 @@
      * {@link Call#removeExtras(List)}.
      *
      * @param extras The new extras bundle.
-     * @hide
      */
     public void onExtrasChanged(Bundle extras) {}
 
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ff220f3..5843fbb 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -35,6 +35,7 @@
 import android.view.Surface;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -100,7 +101,6 @@
      * <p>
      * A connection can only be in this state if the {@link #PROPERTY_IS_EXTERNAL_CALL} property and
      * {@link #CAPABILITY_CAN_PULL_CALL} capability bits are set on the connection.
-     * @hide
      */
     public static final int STATE_PULLING_CALL = 7;
 
@@ -284,7 +284,6 @@
      * <p>
      * Should only be set on a {@code Connection} where {@link #PROPERTY_IS_EXTERNAL_CALL}
      * is set.
-     * @hide
      */
     public static final int CAPABILITY_CAN_PULL_CALL = 0x01000000;
 
@@ -332,13 +331,16 @@
      * external connections.  Only those {@link InCallService}s which have the
      * {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true} in its
      * manifest will see external connections.
-     * @hide
      */
     public static final int PROPERTY_IS_EXTERNAL_CALL = 1<<4;
 
+    /**
+     * Indicates that the connection has CDMA Enhanced Voice Privacy enabled.
+     */
+    public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 1<<5;
 
     //**********************************************************************************************
-    // Next PROPERTY value: 1<<5
+    // Next PROPERTY value: 1<<6
     //**********************************************************************************************
 
     /**
@@ -391,10 +393,15 @@
      * {@link Call.Details#PROPERTY_IS_EXTERNAL_CALL} and
      * {@link Call.Details#CAPABILITY_CAN_PULL_CALL}, but the {@link ConnectionService} could not
      * pull the external call due to an error condition.
-     * @hide
      */
     public static final String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
 
+    /**
+     * Connection event used to inform {@link InCallService}s when the merging of two calls has
+     * failed. The User Interface should use this message to inform the user of the error.
+     */
+    public static final String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED";
+
     // Flag controlling whether PII is emitted into the logs
     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
 
@@ -510,13 +517,6 @@
         return builder.toString();
     }
 
-    /**
-     * Builds a string representation of a properties bit-mask.
-     *
-     * @param properties The properties bit-mask.
-     * @return String representation.
-     * @hide
-     */
     public static String propertiesToString(int properties) {
         StringBuilder builder = new StringBuilder();
         builder.append("[Properties:");
@@ -541,6 +541,10 @@
             builder.append(" PROPERTY_IS_EXTERNAL_CALL");
         }
 
+        if (can(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
+            builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
+        }
+
         builder.append("]");
         return builder.toString();
     }
@@ -1385,6 +1389,15 @@
 
     /**
      * Returns the extras associated with this connection.
+     * <p>
+     * Extras should be updated using {@link #putExtras(Bundle)}.
+     * <p>
+     * Telecom or an {@link InCallService} can also update the extras via
+     * {@link android.telecom.Call#putExtras(Bundle)}, and
+     * {@link Call#removeExtras(List)}.
+     * <p>
+     * The connection is notified of changes to the extras made by Telecom or an
+     * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
      *
      * @return The extras associated with this connection.
      */
@@ -1493,7 +1506,6 @@
 
     /**
      * Returns the connection's properties, as a bit mask of the {@code PROPERTY_*} constants.
-     * @hide
      */
     public final int getConnectionProperties() {
         return mConnectionProperties;
@@ -1699,7 +1711,6 @@
      * Sets the connection's properties as a bit mask of the {@code PROPERTY_*} constants.
      *
      * @param connectionProperties The new connection properties.
-     * @hide
      */
     public final void setConnectionProperties(int connectionProperties) {
         checkImmutable();
@@ -1884,6 +1895,8 @@
      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
      *
      * @param extras The extras associated with this {@code Connection}.
+     * @deprecated Use {@link #putExtras(Bundle)} to add extras.  Use {@link #removeExtras(List)}
+     * to remove extras.
      */
     public final void setExtras(@Nullable Bundle extras) {
         checkImmutable();
@@ -1924,7 +1937,6 @@
      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
      *
      * @param extras The extras to add.
-     * @hide
      */
     public final void putExtras(@NonNull Bundle extras) {
         checkImmutable();
@@ -1988,10 +2000,9 @@
     }
 
     /**
-     * Removes an extra from this {@code Connection}.
+     * Removes extras from this {@code Connection}.
      *
-     * @param keys The key of the extra key to remove.
-     * @hide
+     * @param keys The keys of the extras to remove.
      */
     public final void removeExtras(List<String> keys) {
         synchronized (mExtrasLock) {
@@ -2008,6 +2019,15 @@
     }
 
     /**
+     * Removes extras from this {@code Connection}.
+     *
+     * @param keys The keys of the extras to remove.
+     */
+    public final void removeExtras(String ... keys) {
+        removeExtras(Arrays.asList(keys));
+    }
+
+    /**
      * Notifies this Connection that the {@link #getAudioState()} property has a new value.
      *
      * @param state The new connection audio state.
@@ -2129,7 +2149,6 @@
      * capability and {@link Connection#PROPERTY_IS_EXTERNAL_CALL} property bits must be set.
      * <p>
      * For more information on external calls, see {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
-     * @hide
      */
     public void onPullExternalCall() {}
 
@@ -2142,7 +2161,6 @@
      *
      * @param event The call event.
      * @param extras Extras associated with the call event.
-     * @hide
      */
     public void onCallEvent(String event, Bundle extras) {}
 
@@ -2155,7 +2173,6 @@
      * {@link Call#removeExtras(List)}.
      *
      * @param extras The new extras bundle.
-     * @hide
      */
     public void onExtrasChanged(Bundle extras) {}
 
@@ -2340,7 +2357,6 @@
      *
      * @param event The connection event.
      * @param extras Bundle containing extra information associated with the event.
-     * @hide
      */
     public void sendConnectionEvent(String event, Bundle extras) {
         for (Listener l : mListeners) {
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 65437d9..cf73d4f 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -67,13 +67,11 @@
     /**
      * Disconnected because the user did not locally answer the incoming call, but it was answered
      * on another device where the call was ringing.
-     * @hide
      */
     public static final int ANSWERED_ELSEWHERE = 11;
 
     /**
      * Disconnected because the call was pulled from the current device to another device.
-     * @hide
      */
     public static final int CALL_PULLED = 12;
 
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index e2399ff..df6715d 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -457,7 +457,6 @@
      * @param call The call the event is associated with.
      * @param event The event.
      * @param extras Any associated extras.
-     * @hide
      */
     public void onConnectionEvent(Call call, String event, Bundle extras) {
     }
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index e7c9672..0ee9bab 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -20,11 +20,168 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * @hide
  */
 @SystemApi
 public class ParcelableCallAnalytics implements Parcelable {
+    public static final class AnalyticsEvent implements Parcelable {
+        public static final int SET_SELECT_PHONE_ACCOUNT = 0;
+        public static final int SET_ACTIVE = 1;
+        public static final int SET_DISCONNECTED = 2;
+        public static final int START_CONNECTION = 3;
+        public static final int SET_DIALING = 4;
+        public static final int BIND_CS = 5;
+        public static final int CS_BOUND = 6;
+        public static final int REQUEST_ACCEPT = 7;
+        public static final int REQUEST_REJECT = 8;
+
+        public static final int SCREENING_SENT = 100;
+        public static final int SCREENING_COMPLETED = 101;
+        public static final int DIRECT_TO_VM_INITIATED = 102;
+        public static final int DIRECT_TO_VM_FINISHED = 103;
+        public static final int BLOCK_CHECK_INITIATED = 104;
+        public static final int BLOCK_CHECK_FINISHED = 105;
+        public static final int FILTERING_INITIATED = 106;
+        public static final int FILTERING_COMPLETED = 107;
+        public static final int FILTERING_TIMED_OUT = 108;
+
+        public static final int SKIP_RINGING = 200;
+        public static final int SILENCE = 201;
+        public static final int MUTE = 202;
+        public static final int UNMUTE = 203;
+        public static final int AUDIO_ROUTE_BT = 204;
+        public static final int AUDIO_ROUTE_EARPIECE = 205;
+        public static final int AUDIO_ROUTE_HEADSET = 206;
+        public static final int AUDIO_ROUTE_SPEAKER = 207;
+
+        public static final int CONFERENCE_WITH = 300;
+        public static final int SPLIT_CONFERENCE = 301;
+        public static final int SET_PARENT = 302;
+
+        public static final int REQUEST_HOLD = 400;
+        public static final int REQUEST_UNHOLD = 401;
+        public static final int REMOTELY_HELD = 402;
+        public static final int REMOTELY_UNHELD = 403;
+        public static final int SET_HOLD = 404;
+        public static final int SWAP = 405;
+
+        public static final int REQUEST_PULL = 500;
+
+
+        public static final Parcelable.Creator<AnalyticsEvent> CREATOR =
+                new Parcelable.Creator<AnalyticsEvent> () {
+
+                    @Override
+                    public AnalyticsEvent createFromParcel(Parcel in) {
+                        return new AnalyticsEvent(in);
+                    }
+
+                    @Override
+                    public AnalyticsEvent[] newArray(int size) {
+                        return new AnalyticsEvent[size];
+                    }
+                };
+
+        private int mEventName;
+        private long mTimeSinceLastEvent;
+
+        public AnalyticsEvent(int eventName, long timestamp) {
+            mEventName = eventName;
+            mTimeSinceLastEvent = timestamp;
+        }
+
+        AnalyticsEvent(Parcel in) {
+            mEventName = in.readInt();
+            mTimeSinceLastEvent = in.readLong();
+        }
+
+        public int getEventName() {
+            return mEventName;
+        }
+
+        public long getTimeSinceLastEvent() {
+            return mTimeSinceLastEvent;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mEventName);
+            out.writeLong(mTimeSinceLastEvent);
+        }
+    }
+
+    public static final class EventTiming implements Parcelable {
+        public static final int ACCEPT_TIMING = 0;
+        public static final int REJECT_TIMING = 1;
+        public static final int DISCONNECT_TIMING = 2;
+        public static final int HOLD_TIMING = 3;
+        public static final int UNHOLD_TIMING = 4;
+        public static final int OUTGOING_TIME_TO_DIALING_TIMING = 5;
+        public static final int BIND_CS_TIMING = 6;
+        public static final int SCREENING_COMPLETED_TIMING = 7;
+        public static final int DIRECT_TO_VM_FINISHED_TIMING = 8;
+        public static final int BLOCK_CHECK_FINISHED_TIMING = 9;
+        public static final int FILTERING_COMPLETED_TIMING = 10;
+        public static final int FILTERING_TIMED_OUT_TIMING = 11;
+
+        public static final int INVALID = 999999;
+
+        public static final Parcelable.Creator<EventTiming> CREATOR =
+                new Parcelable.Creator<EventTiming> () {
+
+                    @Override
+                    public EventTiming createFromParcel(Parcel in) {
+                        return new EventTiming(in);
+                    }
+
+                    @Override
+                    public EventTiming[] newArray(int size) {
+                        return new EventTiming[size];
+                    }
+                };
+
+        private int mName;
+        private long mTime;
+
+        public EventTiming(int name, long time) {
+            this.mName = name;
+            this.mTime = time;
+        }
+
+        private EventTiming(Parcel in) {
+            mName = in.readInt();
+            mTime = in.readLong();
+        }
+
+        public int getName() {
+            return mName;
+        }
+
+        public long getTime() {
+            return mTime;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mName);
+            out.writeLong(mTime);
+        }
+    }
+
     public static final int CALLTYPE_UNKNOWN = 0;
     public static final int CALLTYPE_INCOMING = 1;
     public static final int CALLTYPE_OUTGOING = 2;
@@ -87,10 +244,17 @@
     // Whether the call object was created from an existing connection.
     private final boolean isCreatedFromExistingConnection;
 
+    // A list of events that are associated with this call
+    private final List<AnalyticsEvent> analyticsEvents;
+
+    // A map from event-pair names to their durations.
+    private final List<EventTiming> eventTimings;
+
     public ParcelableCallAnalytics(long startTimeMillis, long callDurationMillis, int callType,
             boolean isAdditionalCall, boolean isInterrupted, int callTechnologies,
             int callTerminationCode, boolean isEmergencyCall, String connectionService,
-            boolean isCreatedFromExistingConnection) {
+            boolean isCreatedFromExistingConnection, List<AnalyticsEvent> analyticsEvents,
+            List<EventTiming> eventTimings) {
         this.startTimeMillis = startTimeMillis;
         this.callDurationMillis = callDurationMillis;
         this.callType = callType;
@@ -101,6 +265,8 @@
         this.isEmergencyCall = isEmergencyCall;
         this.connectionService = connectionService;
         this.isCreatedFromExistingConnection = isCreatedFromExistingConnection;
+        this.analyticsEvents = analyticsEvents;
+        this.eventTimings = eventTimings;
     }
 
     public ParcelableCallAnalytics(Parcel in) {
@@ -114,6 +280,10 @@
         isEmergencyCall = readByteAsBoolean(in);
         connectionService = in.readString();
         isCreatedFromExistingConnection = readByteAsBoolean(in);
+        analyticsEvents = new ArrayList<>();
+        in.readTypedList(analyticsEvents, AnalyticsEvent.CREATOR);
+        eventTimings = new ArrayList<>();
+        in.readTypedList(eventTimings, EventTiming.CREATOR);
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -127,6 +297,8 @@
         writeBooleanAsByte(out, isEmergencyCall);
         out.writeString(connectionService);
         writeBooleanAsByte(out, isCreatedFromExistingConnection);
+        out.writeTypedList(analyticsEvents);
+        out.writeTypedList(eventTimings);
     }
 
     public long getStartTimeMillis() {
@@ -169,6 +341,14 @@
         return isCreatedFromExistingConnection;
     }
 
+    public List<AnalyticsEvent> analyticsEvents() {
+        return analyticsEvents;
+    }
+
+    public List<EventTiming> getEventTimings() {
+        return eventTimings;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index bf6038a..943da6d 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -97,7 +97,6 @@
          *
          * @param conference The {@code RemoteConference} invoking this method.
          * @param connectionProperties The new properties of the {@code RemoteConference}.
-         * @hide
          */
         public void onConnectionPropertiesChanged(
                 RemoteConference conference,
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 8e06659db..dc8eaf6 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -95,7 +95,6 @@
          *
          * @param connection The {@code RemoteConnection} invoking this method.
          * @param connectionProperties The new properties of the {@code RemoteConnection}.
-         * @hide
          */
         public void onConnectionPropertiesChanged(
                 RemoteConnection connection,
@@ -230,7 +229,6 @@
          * @param connection The {@code RemoteConnection} invoking this method.
          * @param event The connection event.
          * @param extras Extras associated with the event.
-         * @hide
          */
         public void onConnectionEvent(RemoteConnection connection, String event, Bundle extras) {}
     }
@@ -738,7 +736,6 @@
      *
      * @return A bitmask of the properties of the {@code RemoteConnection}, as defined in the
      *         {@code PROPERTY_*} constants in class {@link Connection}.
-     * @hide
      */
     public int getConnectionProperties() {
         return mConnectionProperties;
@@ -993,7 +990,6 @@
      * Instructs this {@link RemoteConnection} to pull itself to the local device.
      * <p>
      * See {@link Call#pullExternalCall()} for more information.
-     * @hide
      */
     public void pullExternalCall() {
         try {
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl b/telecomm/java/android/telecom/TelecomAnalytics.aidl
similarity index 94%
rename from telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
rename to telecomm/java/android/telecom/TelecomAnalytics.aidl
index b7e78d1..08ad0a2 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
+++ b/telecomm/java/android/telecom/TelecomAnalytics.aidl
@@ -19,4 +19,4 @@
 /**
  * {@hide}
  */
-parcelable ParcelableCallAnalytics;
+parcelable TelecomAnalytics;
diff --git a/telecomm/java/android/telecom/TelecomAnalytics.java b/telecomm/java/android/telecom/TelecomAnalytics.java
new file mode 100644
index 0000000..6e0d02c
--- /dev/null
+++ b/telecomm/java/android/telecom/TelecomAnalytics.java
@@ -0,0 +1,148 @@
+/*
+ * 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 android.telecom;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class TelecomAnalytics implements Parcelable {
+    public static final Parcelable.Creator<TelecomAnalytics> CREATOR =
+            new Parcelable.Creator<TelecomAnalytics> () {
+
+                @Override
+                public TelecomAnalytics createFromParcel(Parcel in) {
+                    return new TelecomAnalytics(in);
+                }
+
+                @Override
+                public TelecomAnalytics[] newArray(int size) {
+                    return new TelecomAnalytics[size];
+                }
+            };
+
+    public static final class SessionTiming extends TimedEvent<Integer> implements Parcelable {
+        public static final Parcelable.Creator<SessionTiming> CREATOR =
+                new Parcelable.Creator<SessionTiming> () {
+
+                    @Override
+                    public SessionTiming createFromParcel(Parcel in) {
+                        return new SessionTiming(in);
+                    }
+
+                    @Override
+                    public SessionTiming[] newArray(int size) {
+                        return new SessionTiming[size];
+                    }
+                };
+
+        public static final int ICA_ANSWER_CALL = 1;
+        public static final int ICA_REJECT_CALL = 2;
+        public static final int ICA_DISCONNECT_CALL = 3;
+        public static final int ICA_HOLD_CALL = 4;
+        public static final int ICA_UNHOLD_CALL = 5;
+        public static final int ICA_MUTE = 6;
+        public static final int ICA_SET_AUDIO_ROUTE = 7;
+        public static final int ICA_CONFERENCE = 8;
+
+        public static final int CSW_HANDLE_CREATE_CONNECTION_COMPLETE = 100;
+        public static final int CSW_SET_ACTIVE = 101;
+        public static final int CSW_SET_RINGING = 102;
+        public static final int CSW_SET_DIALING = 103;
+        public static final int CSW_SET_DISCONNECTED = 104;
+        public static final int CSW_SET_ON_HOLD = 105;
+        public static final int CSW_REMOVE_CALL = 106;
+        public static final int CSW_SET_IS_CONFERENCED = 107;
+        public static final int CSW_ADD_CONFERENCE_CALL = 108;
+
+        private int mId;
+        private long mTime;
+
+        public SessionTiming(int id, long time) {
+            this.mId = id;
+            this.mTime = time;
+        }
+
+        private SessionTiming(Parcel in) {
+            mId = in.readInt();
+            mTime = in.readLong();
+        }
+
+        @Override
+        public Integer getKey() {
+            return mId;
+        }
+
+        @Override
+        public long getTime() {
+            return mTime;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mId);
+            out.writeLong(mTime);
+        }
+    }
+
+    private List<SessionTiming> mSessionTimings;
+    private List<ParcelableCallAnalytics> mCallAnalytics;
+
+    public TelecomAnalytics(List<SessionTiming> sessionTimings,
+            List<ParcelableCallAnalytics> callAnalytics) {
+        this.mSessionTimings = sessionTimings;
+        this.mCallAnalytics = callAnalytics;
+    }
+
+    private TelecomAnalytics(Parcel in) {
+        mSessionTimings = new ArrayList<>();
+        in.readTypedList(mSessionTimings, SessionTiming.CREATOR);
+        mCallAnalytics = new ArrayList<>();
+        in.readTypedList(mCallAnalytics, ParcelableCallAnalytics.CREATOR);
+    }
+
+    public List<SessionTiming> getSessionTimings() {
+        return mSessionTimings;
+    }
+
+    public List<ParcelableCallAnalytics> getCallAnalytics() {
+        return mCallAnalytics;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeTypedList(mSessionTimings);
+        out.writeTypedList(mCallAnalytics);
+    }
+}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index da0d048..f12886a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -348,7 +348,6 @@
      * informed of external calls should set this meta-data to {@code true} in the manifest
      * registration of their {@link InCallService}.  By default, the {@link InCallService} will NOT
      * be informed of external calls.
-     * @hide
      */
     public static final String METADATA_INCLUDE_EXTERNAL_CALLS =
             "android.telecom.INCLUDE_EXTERNAL_CALLS";
@@ -1444,9 +1443,9 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.DUMP)
-    public List<ParcelableCallAnalytics> dumpAnalytics() {
+    public TelecomAnalytics dumpAnalytics() {
         ITelecomService service = getTelecomService();
-        List<ParcelableCallAnalytics> result = null;
+        TelecomAnalytics result = null;
         if (service != null) {
             try {
                 result = service.dumpCallAnalytics();
diff --git a/telecomm/java/android/telecom/TimedEvent.java b/telecomm/java/android/telecom/TimedEvent.java
new file mode 100644
index 0000000..e484e79
--- /dev/null
+++ b/telecomm/java/android/telecom/TimedEvent.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.telecom;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public abstract class TimedEvent<T> {
+    public abstract long getTime();
+    public abstract T getKey();
+
+    public static <T> Map<T, Double> averageTimings(Collection<? extends TimedEvent<T>> events) {
+        HashMap<T, Integer> counts = new HashMap<>();
+        HashMap<T, Double> result = new HashMap<>();
+
+        for (TimedEvent<T> entry : events) {
+            if (counts.containsKey(entry.getKey())) {
+                counts.put(entry.getKey(), counts.get(entry.getKey()) + 1);
+                result.put(entry.getKey(), result.get(entry.getKey()) + entry.getTime());
+            } else {
+                counts.put(entry.getKey(), 1);
+                result.put(entry.getKey(), (double) entry.getTime());
+            }
+        }
+
+        for (Map.Entry<T, Double> entry : result.entrySet()) {
+            result.put(entry.getKey(), entry.getValue() / counts.get(entry.getKey()));
+        }
+
+        return result;
+    }
+}
+
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 871565d..5c412e7 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -18,7 +18,7 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
-import android.telecom.ParcelableCallAnalytics;
+import android.telecom.TelecomAnalytics;
 import android.telecom.PhoneAccountHandle;
 import android.net.Uri;
 import android.os.Bundle;
@@ -148,7 +148,7 @@
     /**
     * @see TelecomServiceImpl#dumpCallAnalytics
     */
-    List<ParcelableCallAnalytics> dumpCallAnalytics();
+    TelecomAnalytics dumpCallAnalytics();
 
     //
     // Internal system apis relating to call management.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ff7ca62..44d89c1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -530,6 +530,17 @@
     public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
 
     /**
+     * Determines whether video conference calls are supported by a carrier.  When {@code true},
+     * video calls can be merged into conference calls, {@code false} otherwiwse.
+     * <p>
+     * Note: even if video conference calls are not supported, audio calls may be merged into a
+     * conference if {@link #KEY_SUPPORT_CONFERENCE_CALL_BOOL} is {@code true}.
+     * @hide
+     */
+    public static final String KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL =
+            "support_video_conference_call_bool";
+
+    /**
      * Determine whether user can toggle Enhanced 4G LTE Mode in Settings.
      */
     public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
@@ -582,6 +593,14 @@
     public static final String KEY_WFC_DATA_SPN_FORMAT_IDX_INT = "wfc_data_spn_format_idx_int";
 
     /**
+     * The Component Name of the activity that can setup the emergency addrees for WiFi Calling
+     * as per carrier requirement.
+     * @hide
+     */
+     public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING =
+            "wfc_emergency_address_carrier_app_string";
+
+    /**
      * Boolean to decide whether to use #KEY_CARRIER_NAME_STRING from CarrierConfig app.
      * @hide
      */
@@ -594,7 +613,6 @@
      */
     public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
 
-
     /**
      * If this is true, the SIM card (through Customer Service Profile EF file) will be able to
      * prevent manual operator selection. If false, this SIM setting will be ignored and manual
@@ -615,6 +633,13 @@
     public static final String KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL =
             "broadcast_emergency_call_state_changes_bool";
 
+    /**
+     * Cell broadcast additional channels enbled by the carrier
+     * @hide
+     */
+    public static final String KEY_CARRIER_ADDITIONAL_CBS_CHANNELS_STRINGS =
+            "carrier_additional_cbs_channels_strings";
+
     // These variables are used by the MMS service and exposed through another API, {@link
     // SmsManager}. The variable names and string values are copied from there.
     public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
@@ -658,10 +683,44 @@
      * example:
      * <item>com.google.android.carrierPackageName</item>
      * <item>com.google.android.carrierPackageName.CarrierActivityName</item>
+     * The ComponentName of the carrier activity that can setup the device and activate with the
+     * network as part of the Setup Wizard flow.
      * @hide
      */
-     public static final String KEY_SIM_PROVISIONING_STATUS_DETECTION_CARRIER_APP_STRING_ARRAY =
-            "sim_state_detection_carrier_app_string_array";
+     public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
+
+    /**
+     * A list of component name of carrier signalling receivers which are interested in intent
+     * android.intent.action.CARRIER_SIGNAL_REDIRECTED.
+     * Example:
+     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
+     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
+     * @hide
+     */
+    public static final String KEY_SIGNAL_REDIRECTION_RECEIVER_STRING_ARRAY =
+            "signal_redirection_receiver_string_array";
+
+    /**
+     * A list of component name of carrier signalling receivers which are interested in intent
+     * android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED.
+     * Example:
+     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
+     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
+     * @hide
+     */
+    public static final String KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY =
+            "signal_dcfailure_receiver_string_array";
+
+    /**
+     * A list of component name of carrier signalling receivers which are interested in intent
+     * android.intent.action.CARRIER_SIGNAL_PCO_VALUE.
+     * Example:
+     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
+     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
+     * @hide
+     */
+    public static final String KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY =
+            "signal_pco_receiver_string_array";
 
     /**
      * Determines whether the carrier supports making non-emergency phone calls while the phone is
@@ -808,6 +867,7 @@
         sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
         sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
         sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
@@ -816,6 +876,7 @@
         sDefaults.putStringArray(KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY, null);
         sDefaults.putInt(KEY_WFC_SPN_FORMAT_IDX_INT, 0);
         sDefaults.putInt(KEY_WFC_DATA_SPN_FORMAT_IDX_INT, 0);
+        sDefaults.putString(KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING, "");
         sDefaults.putBoolean(KEY_CONFIG_WIFI_DISABLE_IN_ECBM, false);
         sDefaults.putBoolean(KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
         sDefaults.putString(KEY_CARRIER_NAME_STRING, "");
@@ -856,9 +917,11 @@
         sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
         sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT);
 
-        // Used for Sim card State detection app
-        sDefaults.putStringArray(KEY_SIM_PROVISIONING_STATUS_DETECTION_CARRIER_APP_STRING_ARRAY,
-                null);
+        // Carrier Signalling Receivers
+        sDefaults.putStringArray(KEY_SIGNAL_REDIRECTION_RECEIVER_STRING_ARRAY, null);
+        sDefaults.putStringArray(KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY, null);
+        sDefaults.putStringArray(KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY, null);
+        sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, "");
     }
 
     /**
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 9eb1304..7a9170f 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -194,6 +194,12 @@
      */
     public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50;
 
+    /**
+     * The call was terminated because it was pulled to another device.
+     * {@hide}
+     */
+    public static final int CALL_PULLED = 51;
+
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Please assign the new type the next id value below.
@@ -318,7 +324,9 @@
         case CDMA_ALREADY_ACTIVATED:
             return "CDMA_ALREADY_ACTIVATED";
         case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
-                return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
+            return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
+        case CALL_PULLED:
+            return "CALL_PULLED";
         default:
             return "INVALID: " + cause;
         }
diff --git a/telephony/java/android/telephony/PcoData.aidl b/telephony/java/android/telephony/PcoData.aidl
new file mode 100644
index 0000000..a93b8d3
--- /dev/null
+++ b/telephony/java/android/telephony/PcoData.aidl
@@ -0,0 +1,21 @@
+/*
+**
+** 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 android.telephony;
+
+parcelable PcoData;
+
diff --git a/telephony/java/android/telephony/PcoData.java b/telephony/java/android/telephony/PcoData.java
new file mode 100644
index 0000000..3e735e7
--- /dev/null
+++ b/telephony/java/android/telephony/PcoData.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains Carrier-specific (and opaque) Protocol configuration Option
+ * Data.  In general this is only passed on to carrier-specific applications
+ * for interpretation.
+ *
+ * @hide
+ */
+public class PcoData implements Parcelable {
+
+    public final int cid;
+    public final String bearerProto;
+    public final int pcoId;
+    public final byte[] contents;
+
+    public PcoData(int cid, String bearerProto, int pcoId, byte[]contents) {
+        this.cid = cid;
+        this.bearerProto = bearerProto;
+        this.pcoId = pcoId;
+        this.contents = contents;
+    }
+
+    public PcoData(Parcel in) {
+        cid = in.readInt();
+        bearerProto = in.readString();
+        pcoId = in.readInt();
+        contents = in.createByteArray();
+    }
+
+    /**
+     * {@link Parcelable#writeToParcel}
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(cid);
+        out.writeString(bearerProto);
+        out.writeInt(pcoId);
+        out.writeByteArray(contents);
+    }
+
+    /**
+     * {@link Parcelable#describeContents}
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@link Parcelable.Creator}
+     *
+     * @hide
+     */
+    public static final Parcelable.Creator<PcoData> CREATOR = new Parcelable.Creator() {
+        public PcoData createFromParcel(Parcel in) {
+            return new PcoData(in);
+        }
+
+        public PcoData[] newArray(int size) {
+            return new PcoData[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + ", contents[" +
+                contents.length + "])";
+    }
+}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 3c30251..6b2ae3e 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -30,6 +30,7 @@
 import android.os.SystemProperties;
 import android.provider.Contacts;
 import android.provider.ContactsContract;
+import android.telecom.PhoneAccount;
 import android.text.Editable;
 import android.text.Spannable;
 import android.text.SpannableStringBuilder;
@@ -2599,6 +2600,48 @@
     }
 
     /**
+     * Given a {@link Uri} with a {@code sip} scheme, attempts to build an equivalent {@code tel}
+     * scheme {@link Uri}.  If the source {@link Uri} does not contain a valid number, or is not
+     * using the {@code sip} scheme, the original {@link Uri} is returned.
+     *
+     * @param source The {@link Uri} to convert.
+     * @return The equivalent {@code tel} scheme {@link Uri}.
+     *
+     * @hide
+     */
+    public static Uri convertSipUriToTelUri(Uri source) {
+        // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
+        // Per RFC3261, the "user" can be a telephone number.
+        // For example: sip:1650555121;phone-context=blah.com@host.com
+        // In this case, the phone number is in the user field of the URI, and the parameters can be
+        // ignored.
+        //
+        // A SIP URI can also specify a phone number in a format similar to:
+        // sip:+1-212-555-1212@something.com;user=phone
+        // In this case, the phone number is again in user field and the parameters can be ignored.
+        // We can get the user field in these instances by splitting the string on the @, ;, or :
+        // and looking at the first found item.
+
+        String scheme = source.getScheme();
+
+        if (!PhoneAccount.SCHEME_SIP.equals(scheme)) {
+            // Not a sip URI, bail.
+            return source;
+        }
+
+        String number = source.getSchemeSpecificPart();
+        String numberParts[] = number.split("[@;:]");
+
+        if (numberParts.length == 0) {
+            // Number not found, bail.
+            return source;
+        }
+        number = numberParts[0];
+
+        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
+    }
+
+    /**
      * This function handles the plus code conversion
      * If the number format is
      * 1)+1NANP,remove +,
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index b5cf212e..6229ed9 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -90,14 +90,6 @@
     private int mDataRoaming;
 
     /**
-     * Sim Provisioning Status:
-     * {@See SubscriptionManager#SIM_PROVISIONED}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_COLD}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_OUT_OF_CREDIT}
-     */
-    private int mSimProvisioningStatus;
-
-    /**
      * SIM Icon bitmap
      */
     private Bitmap mIconBitmap;
@@ -122,7 +114,7 @@
      */
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
-            Bitmap icon, int mcc, int mnc, String countryIso, int simProvisioningStatus) {
+            Bitmap icon, int mcc, int mnc, String countryIso) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -136,7 +128,6 @@
         this.mMcc = mcc;
         this.mMnc = mnc;
         this.mCountryIso = countryIso;
-        this.mSimProvisioningStatus = simProvisioningStatus;
     }
 
     /**
@@ -273,17 +264,6 @@
     }
 
     /**
-     * @return Sim Provisioning Status
-     * {@See SubscriptionManager#SIM_PROVISIONED}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_COLD}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_OUT_OF_CREDIT}
-     * @hide
-     */
-    public int getSimProvisioningStatus() {
-        return this.mSimProvisioningStatus;
-    }
-
-    /**
      * @return the MCC.
      */
     public int getMcc() {
@@ -319,12 +299,10 @@
             int mcc = source.readInt();
             int mnc = source.readInt();
             String countryIso = source.readString();
-            int simProvisioningStatus = source.readInt();
             Bitmap iconBitmap = Bitmap.CREATOR.createFromParcel(source);
 
             return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
-                    nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
-                    simProvisioningStatus);
+                    nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso);
         }
 
         @Override
@@ -347,7 +325,6 @@
         dest.writeInt(mMcc);
         dest.writeInt(mMnc);
         dest.writeString(mCountryIso);
-        dest.writeInt(mSimProvisioningStatus);
         mIconBitmap.writeToParcel(dest, flags);
     }
 
@@ -378,6 +355,6 @@
                 + " displayName=" + mDisplayName + " carrierName=" + mCarrierName
                 + " nameSource=" + mNameSource + " iconTint=" + mIconTint
                 + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
-                + " mnc " + mMnc + " SimProvisioningStatus " + mSimProvisioningStatus +"}";
+                + " mnc " + mMnc + "}";
     }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c49966a..dd6f9cb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -232,25 +232,12 @@
     /** Indicates that data roaming is disabled for a subscription */
     public static final int DATA_ROAMING_DISABLE = 0;
 
-    /** Sim provisioning status: provisioned */
-    /** @hide */
-    public static final int SIM_PROVISIONED = 0;
-
-    /** Sim provisioning status: un-provisioned due to cold sim */
-    /** @hide */
-    public static final int SIM_UNPROVISIONED_COLD = 1;
-
-    /** Sim provisioning status: un-provisioned due to out of credit */
-    /** @hide */
-    public static final int SIM_UNPROVISIONED_OUT_OF_CREDIT = 2;
-
-    /** Maximum possible sim provisioning status */
-    /** @hide */
-    public static final int MAX_SIM_PROVISIONING_STATUS = SIM_UNPROVISIONED_OUT_OF_CREDIT;
-
     /** @hide */
     public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
 
+    /** @hide */
+    public static final int SIM_PROVISIONED = 0;
+
     /**
      * TelephonyProvider column name for the MCC associated with a SIM.
      * <P>Type: INTEGER (int)</P>
@@ -843,40 +830,6 @@
     }
 
     /**
-     * Set Sim Provisioning Status by subscription ID
-     * @param simProvisioningStatus with the subscription
-     * {@See SubscriptionManager#SIM_PROVISIONED}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_COLD}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_OUT_OF_CREDIT}
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
-     * @hide
-     */
-    public int setSimProvisioningStatus(int simProvisioningStatus, int subId) {
-        if (VDBG) {
-            logd("[setSimProvisioningStatus]+ status:" + simProvisioningStatus + " subId:" + subId);
-        }
-        if (simProvisioningStatus < 0 || simProvisioningStatus > MAX_SIM_PROVISIONING_STATUS ||
-                !isValidSubscriptionId(subId)) {
-            logd("[setSimProvisioningStatus]- fail");
-            return -1;
-        }
-
-        int result = 0;
-
-        try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
-            if (iSub != null) {
-                result = iSub.setSimProvisioningStatus(simProvisioningStatus, subId);
-            }
-        } catch (RemoteException ex) {
-            // ignore it
-        }
-        return result;
-    }
-
-    /**
      * Get slotId associated with the subscription.
      * @return slotId as a positive integer or a negative value if an error either
      * SIM_NOT_INSERTED or < 0 if an invalid slot index
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl b/telephony/java/android/telephony/TelephonyHistogram.aidl
similarity index 74%
copy from telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
copy to telephony/java/android/telephony/TelephonyHistogram.aidl
index b7e78d1..8de81cf 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
+++ b/telephony/java/android/telephony/TelephonyHistogram.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright 2016, The Android Open Source Project
+ * 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
+ *      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,
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package android.telecom;
-
+package android.telephony;
 /**
- * {@hide}
+ * @hide
  */
-parcelable ParcelableCallAnalytics;
+parcelable TelephonyHistogram;
diff --git a/telephony/java/android/telephony/TelephonyHistogram.java b/telephony/java/android/telephony/TelephonyHistogram.java
new file mode 100644
index 0000000..9d9ce72
--- /dev/null
+++ b/telephony/java/android/telephony/TelephonyHistogram.java
@@ -0,0 +1,293 @@
+/*
+ * 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 android.telephony;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Parcelable class to store Telephony histogram.
+ * @hide
+ */
+@SystemApi
+public final class TelephonyHistogram implements Parcelable {
+    // Type of Telephony histogram Eg: RIL histogram will have all timing data associated with
+    // RIL calls. Similarly we can have any other Telephony histogram.
+    private final int category;
+
+    // Unique Id identifying a sample within particular category of histogram
+    private final int id;
+
+    // Min time taken in ms
+    private int minTimeMs;
+
+    // Max time taken in ms
+    private int maxTimeMs;
+
+    // Average time taken in ms
+    private int averageTimeMs;
+
+    // Total count of samples
+    private int sampleCount;
+
+    // Array storing time taken for first #RANGE_CALCULATION_COUNT samples of histogram.
+    private int[] initialTimings;
+
+    // Total number of time ranges expected (must be greater than 1)
+    private final int bucketCount;
+
+    // Array storing endpoints of range buckets. Calculated based on values of minTime & maxTime
+    // after totalTimeCount is #RANGE_CALCULATION_COUNT.
+    private final int[] bucketEndPoints;
+
+    // Array storing counts for each time range starting from smallest value range
+    private final int[] bucketCounters;
+
+    /**
+     * Constant for Telephony category
+     */
+    public static final int TELEPHONY_CATEGORY_RIL = 1;
+
+    // Count of Histogram samples after which time buckets are created.
+    private static final int RANGE_CALCULATION_COUNT = 10;
+
+
+    // Constant used to indicate #initialTimings is null while parceling
+    private static final int ABSENT = 0;
+
+    // Constant used to indicate #initialTimings is not null while parceling
+    private static final int PRESENT = 1;
+
+    // Throws exception if #totalBuckets is not greater than one.
+    public TelephonyHistogram (int category, int id, int bucketCount) {
+        if (bucketCount <= 1) {
+            throw new IllegalArgumentException("Invalid number of buckets");
+        }
+        this.category = category;
+        this.id = id;
+        this.minTimeMs = Integer.MAX_VALUE;
+        this.maxTimeMs = 0;
+        this.averageTimeMs = 0;
+        this.sampleCount = 0;
+        initialTimings = new int[RANGE_CALCULATION_COUNT];
+        this.bucketCount = bucketCount;
+        bucketEndPoints = new int[bucketCount - 1];
+        bucketCounters = new int[bucketCount];
+    }
+
+    public TelephonyHistogram(TelephonyHistogram th) {
+        category = th.getCategory();
+        id = th.getId();
+        minTimeMs = th.getMinTime();
+        maxTimeMs = th.getMaxTime();
+        averageTimeMs = th.getAverageTime();
+        sampleCount = th.getSampleCount();
+        initialTimings = th.getInitialTimings();
+        bucketCount = th.getBucketCount();
+        bucketEndPoints = th.getBucketEndPoints();
+        bucketCounters = th.getBucketCounters();
+    }
+
+    public int getCategory() {
+        return category;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public int getMinTime() {
+        return minTimeMs;
+    }
+
+    public int getMaxTime() {
+        return maxTimeMs;
+    }
+
+    public int getAverageTime() {
+        return averageTimeMs;
+    }
+
+    public int getSampleCount () {
+        return sampleCount;
+    }
+
+    private int[] getInitialTimings() {
+        return initialTimings;
+    }
+
+    public int getBucketCount() {
+        return bucketCount;
+    }
+
+    public int[] getBucketEndPoints() {
+        return getDeepCopyOfArray(bucketEndPoints);
+    }
+
+    public int[] getBucketCounters() {
+        return getDeepCopyOfArray(bucketCounters);
+    }
+
+    private int[] getDeepCopyOfArray(int[] array) {
+        int[] clone = new int[array.length];
+        System.arraycopy(array, 0, clone, 0, array.length);
+        return clone;
+    }
+
+    private void addToBucketCounter(int time) {
+        int i;
+        for (i = 0; i < bucketEndPoints.length; i++) {
+            if (time <= bucketEndPoints[i]) {
+                bucketCounters[i]++;
+                return;
+            }
+        }
+        bucketCounters[i]++;
+    }
+
+    // Add new value of time taken
+    // This function updates minTime, maxTime, averageTime & totalTimeCount every time it is
+    // called. initialTimings[] is updated if totalTimeCount <= #RANGE_CALCULATION_COUNT. When
+    // totalTimeCount = RANGE_CALCULATION_COUNT, based on the min, max time & the number of buckets
+    // expected, bucketEndPoints[] would be calculated. Then bucketCounters[] would be filled up
+    // using values stored in initialTimings[]. Thereafter bucketCounters[] will always be updated.
+    public void addTimeTaken(int time) {
+        // Initialize all fields if its first entry or if integer overflow is going to occur while
+        // trying to calculate averageTime
+        if (sampleCount == 0 || (sampleCount == Integer.MAX_VALUE)) {
+            if (sampleCount == 0) {
+                minTimeMs = time;
+                maxTimeMs = time;
+                averageTimeMs = time;
+            } else {
+                initialTimings = new int[RANGE_CALCULATION_COUNT];
+            }
+            sampleCount = 1;
+            Arrays.fill(initialTimings, 0);
+            initialTimings[0] = time;
+            Arrays.fill(bucketEndPoints, 0);
+            Arrays.fill(bucketCounters, 0);
+        } else {
+            if (time < minTimeMs) {
+                minTimeMs = time;
+            }
+            if (time > maxTimeMs) {
+                maxTimeMs = time;
+            }
+            long totalTime = ((long)averageTimeMs) * sampleCount + time;
+            averageTimeMs = (int)(totalTime/++sampleCount);
+
+            if (sampleCount < RANGE_CALCULATION_COUNT) {
+                initialTimings[sampleCount - 1] = time;
+            } else if (sampleCount == RANGE_CALCULATION_COUNT) {
+                initialTimings[sampleCount - 1] = time;
+
+                // Calculate bucket endpoints based on bucketCount expected
+                for (int i = 1; i < bucketCount; i++) {
+                    int endPt = minTimeMs + (i * (maxTimeMs - minTimeMs)) / bucketCount;
+                    bucketEndPoints[i - 1] = endPt;
+                }
+
+                // Use values stored in initialTimings[] to update bucketCounters
+                for (int j = 0; j < RANGE_CALCULATION_COUNT; j++) {
+                    addToBucketCounter(initialTimings[j]);
+                }
+                initialTimings = null;
+            } else {
+                addToBucketCounter(time);
+            }
+
+        }
+    }
+
+    public String toString() {
+        String basic = " Histogram id = " + id + " Time(ms): min = " + minTimeMs + " max = "
+                + maxTimeMs + " avg = " + averageTimeMs + " Count = " + sampleCount;
+        if (sampleCount < RANGE_CALCULATION_COUNT) {
+            return basic;
+        } else {
+            StringBuffer intervals = new StringBuffer(" Interval Endpoints:");
+            for (int i = 0; i < bucketEndPoints.length; i++) {
+                intervals.append(" " + bucketEndPoints[i]);
+            }
+            intervals.append(" Interval counters:");
+            for (int i = 0; i < bucketCounters.length; i++) {
+                intervals.append(" " + bucketCounters[i]);
+            }
+            return basic + intervals;
+        }
+    }
+
+    public static final Parcelable.Creator<TelephonyHistogram> CREATOR =
+            new Parcelable.Creator<TelephonyHistogram> () {
+
+                @Override
+                public TelephonyHistogram createFromParcel(Parcel in) {
+                    return new TelephonyHistogram(in);
+                }
+
+                @Override
+                public TelephonyHistogram[] newArray(int size) {
+                    return new TelephonyHistogram[size];
+                }
+            };
+
+    public TelephonyHistogram(Parcel in) {
+        category = in.readInt();
+        id = in.readInt();
+        minTimeMs = in.readInt();
+        maxTimeMs = in.readInt();
+        averageTimeMs = in.readInt();
+        sampleCount = in.readInt();
+        if (in.readInt() == PRESENT) {
+            initialTimings = new int[RANGE_CALCULATION_COUNT];
+            in.readIntArray(initialTimings);
+        }
+        bucketCount = in.readInt();
+        bucketEndPoints = new int[bucketCount - 1];
+        in.readIntArray(bucketEndPoints);
+        bucketCounters = new int[bucketCount];
+        in.readIntArray(bucketCounters);
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(category);
+        out.writeInt(id);
+        out.writeInt(minTimeMs);
+        out.writeInt(maxTimeMs);
+        out.writeInt(averageTimeMs);
+        out.writeInt(sampleCount);
+        if (initialTimings == null) {
+            out.writeInt(ABSENT);
+        } else {
+            out.writeInt(PRESENT);
+            out.writeIntArray(initialTimings);
+        }
+        out.writeInt(bucketCount);
+        out.writeIntArray(bucketEndPoints);
+        out.writeIntArray(bucketCounters);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0875ff9..78e15bf 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -34,8 +34,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.service.carrier.CarrierIdentifier;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyHistogram;
 import android.util.Log;
 
 import com.android.internal.telecom.ITelecomService;
@@ -50,6 +52,7 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -1756,6 +1759,11 @@
      *@hide
      */
     public static final int SIM_STATE_CARD_IO_ERROR = 8;
+    /** SIM card state: SIM Card restricted, present but not usable due to
+     * carrier restrictions.
+     *@hide
+     */
+    public static final int SIM_STATE_CARD_RESTRICTED = 9;
 
     /**
      * @return true if a ICC card is present
@@ -1909,7 +1917,7 @@
         return getSimOperatorNumericForPhone(phoneId);
     }
 
-   /**
+    /**
      * Returns the MCC+MNC (mobile country code + mobile network code) of the
      * provider of the SIM for a particular subscription. 5 or 6 decimal digits.
      * <p>
@@ -2461,6 +2469,102 @@
     }
 
     /**
+     * Enables the visual voicemail SMS filter for a phone account. When the filter is
+     * enabled, Incoming SMS messages matching the OMTP VVM SMS interface will be redirected to the
+     * visual voicemail client with
+     * {@link android.provider.VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED}.
+     *
+     * <p>This takes effect only when the caller is the default dialer. The enabled status and
+     * settings persist through default dialer changes, but the filter will only honor the setting
+     * set by the current default dialer.
+     *
+     *
+     * @param subId The subscription id of the phone account.
+     * @param settings The settings for the filter.
+     */
+    /** @hide */
+    public void enableVisualVoicemailSmsFilter(int subId,
+            VisualVoicemailSmsFilterSettings settings) {
+        if(settings == null){
+            throw new IllegalArgumentException("Settings cannot be null");
+        }
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.enableVisualVoicemailSmsFilter(mContext.getOpPackageName(), subId,
+                        settings);
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+    }
+
+    /**
+     * Disables the visual voicemail SMS filter for a phone account.
+     *
+     * <p>This takes effect only when the caller is the default dialer. The enabled status and
+     * settings persist through default dialer changes, but the filter will only honor the setting
+     * set by the current default dialer.
+     */
+    /** @hide */
+    public void disableVisualVoicemailSmsFilter(int subId) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.disableVisualVoicemailSmsFilter(mContext.getOpPackageName(), subId);
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+    }
+
+    /**
+     * @returns the settings of the visual voicemail SMS filter for a phone account, or {@code null}
+     * if the filter is disabled.
+     *
+     * <p>This takes effect only when the caller is the default dialer. The enabled status and
+     * settings persist through default dialer changes, but the filter will only honor the setting
+     * set by the current default dialer.
+     */
+    /** @hide */
+    @Nullable
+    public VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(int subId) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony
+                        .getVisualVoicemailSmsFilterSettings(mContext.getOpPackageName(), subId);
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+
+        return null;
+    }
+
+    /**
+     * @returns the settings of the visual voicemail SMS filter for a phone account set by the
+     * package, or {@code null} if the filter is disabled.
+     *
+     * <p>Requires the calling app to have READ_PRIVILEGED_PHONE_STATE permission.
+     */
+    /** @hide */
+    @Nullable
+    public VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(String packageName,
+            int subId) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getSystemVisualVoicemailSmsFilterSettings(packageName, subId);
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+
+        return null;
+    }
+
+    /**
      * Returns the voice mail count. Return 0 if unavailable, -1 if there are unread voice messages
      * but the count is unknown.
      * <p>
@@ -5246,4 +5350,170 @@
         }
         return false;
     }
+
+    /**
+     * Return the application ID for the app type like {@link APPTYPE_CSIM}.
+     *
+     * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+     *
+     * @param appType the uicc app type like {@link APPTYPE_CSIM}
+     * @return Application ID for specificied app type or null if no uicc or error.
+     * @hide
+     */
+    public String getAidForAppType(int appType) {
+        return getAidForAppType(getDefaultSubscription(), appType);
+    }
+
+    /**
+     * Return the application ID for the app type like {@link APPTYPE_CSIM}.
+     *
+     * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+     *
+     * @param subId the subscription ID that this request applies to.
+     * @param appType the uicc app type, like {@link APPTYPE_CSIM}
+     * @return Application ID for specificied app type or null if no uicc or error.
+     * @hide
+     */
+    public String getAidForAppType(int subId, int appType) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getAidForAppType(subId, appType);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getAidForAppType", e);
+        }
+        return null;
+    }
+
+    /**
+     * Return the Electronic Serial Number.
+     *
+     * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+     *
+     * @return ESN or null if error.
+     * @hide
+     */
+    public String getEsn() {
+        return getEsn(getDefaultSubscription());
+    }
+
+    /**
+     * Return the Electronic Serial Number.
+     *
+     * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+     *
+     * @param subId the subscription ID that this request applies to.
+     * @return ESN or null if error.
+     * @hide
+     */
+    public String getEsn(int subId) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getEsn(subId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getEsn", e);
+        }
+        return null;
+    }
+
+    /*
+     * Get snapshot of Telephony histograms
+     * @return List of Telephony histograms
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     * @hide
+     */
+    @SystemApi
+    public List<TelephonyHistogram> getTelephonyHistograms() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getTelephonyHistograms();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getTelephonyHistograms", e);
+        }
+        return null;
+    }
+
+    /**
+     * Set the allowed carrier list for slotId
+     * Require system privileges. In the future we may add this to carrier APIs.
+     *
+     * @return The number of carriers set successfully. Should be length of
+     * carrierList on success; -1 on error.
+     * @hide
+     */
+    public int setAllowedCarriers(int slotId, List<CarrierIdentifier> carriers) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.setAllowedCarriers(slotId, carriers);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
+        }
+        return -1;
+    }
+
+    /**
+     * Get the allowed carrier list for slotId.
+     * Require system privileges. In the future we may add this to carrier APIs.
+     *
+     * @return List of {@link android.telephony.CarrierIdentifier}; empty list
+     * means all carriers are allowed.
+     * @hide
+     */
+    public List<CarrierIdentifier> getAllowedCarriers(int slotId) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getAllowedCarriers(slotId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
+        }
+        return new ArrayList<CarrierIdentifier>(0);
+    }
+
+    /**
+     * Action set from carrier signalling broadcast receivers to enable/disable metered apns
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * @param subId the subscription ID that this action applies to.
+     * @param enabled control enable or disable metered apns.
+     * @hide
+     */
+    public void carrierActionSetMeteredApnsEnabled(int subId, boolean enabled) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                service.carrierActionSetMeteredApnsEnabled(subId, enabled);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#carrierActionSetMeteredApnsEnabled", e);
+        }
+    }
+
+    /**
+     * Action set from carrier signalling broadcast receivers to enable/disable radio
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * @param subId the subscription ID that this action applies to.
+     * @param enabled control enable or disable radio.
+     * @hide
+     */
+    public void carrierActionSetRadioEnabled(int subId, boolean enabled) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                service.carrierActionSetRadioEnabled(subId, enabled);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e);
+        }
+    }
 }
+
diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.aidl b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.aidl
new file mode 100644
index 0000000..4b0539d
--- /dev/null
+++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.aidl
@@ -0,0 +1,19 @@
+/*
+* 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 android.telephony;
+
+parcelable VisualVoicemailSmsFilterSettings;
diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
new file mode 100644
index 0000000..5b81027
--- /dev/null
+++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
@@ -0,0 +1,174 @@
+/*
+ * 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 android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class to represent various settings for the visual voicemail SMS filter. When the filter is
+ * enabled, incoming SMS matching the generalized OMTP format:
+ *
+ * <p>[clientPrefix]:[prefix]:([key]=[value];)*
+ *
+ * <p>will be regarded as a visual voicemail SMS, and removed before reaching the SMS provider. The
+ * intent {@link android.provider.VoicemailContract#ACTION_VOICEMAIL_SMS_RECEIVED} will then be sent
+ * to the default dialer with the information extracted from the SMS.
+ *
+ * <p>Use {@link android.telephony.VisualVoicemailSmsFilterSettings.Builder} to construct this
+ * class.
+ *
+ * @see android.telephony.TelephonyManager#enableVisualVoicemailSmsFilter
+ *
+ * @hide
+ */
+public class VisualVoicemailSmsFilterSettings implements Parcelable {
+
+
+    /**
+     * The visual voicemail SMS message does not have to be a data SMS, and can be directed to any
+     * port.
+     *
+     * @hide
+     */
+    public static final int DESTINATION_PORT_ANY = -1;
+
+    /**
+     * The visual voicemail SMS message can be directed to any port, but must be a data SMS.
+     *
+     * @hide
+     */
+    public static final int DESTINATION_PORT_DATA_SMS = -2;
+
+    public static final String DEFAULT_CLIENT_PREFIX = "//VVM";
+    public static final List<String> DEFAULT_ORIGINATING_NUMBERS = Collections.emptyList();
+    public static final int DEFAULT_DESTINATION_PORT = DESTINATION_PORT_ANY;
+
+    /**
+     * Builder class for {@link VisualVoicemailSmsFilterSettings} objects.
+     *
+     * @hide
+     */
+    public static class Builder {
+
+        private String mClientPrefix = DEFAULT_CLIENT_PREFIX;
+        private List<String> mOriginatingNumbers = DEFAULT_ORIGINATING_NUMBERS;
+        private int mDestinationPort = DEFAULT_DESTINATION_PORT;
+
+        public VisualVoicemailSmsFilterSettings build() {
+            return new VisualVoicemailSmsFilterSettings(this);
+        }
+
+        /**
+         * Sets the client prefix for the visual voicemail SMS filter. The client prefix will appear
+         * at the start of a visual voicemail SMS message, followed by a colon(:).
+         */
+        public Builder setClientPrefix(String clientPrefix) {
+            if (clientPrefix == null) {
+                throw new IllegalArgumentException("Client prefix cannot be null");
+            }
+            mClientPrefix = clientPrefix;
+            return this;
+        }
+
+        /**
+         * Sets the originating number whitelist for the visual voicemail SMS filter. If the list is
+         * not null only the SMS messages from a number in the list can be considered as a visual
+         * voicemail SMS. Otherwise, messages from any address will be considered.
+         */
+        public Builder setOriginatingNumbers(List<String> originatingNumbers) {
+            if (originatingNumbers == null) {
+                throw new IllegalArgumentException("Originating numbers cannot be null");
+            }
+            mOriginatingNumbers = originatingNumbers;
+            return this;
+        }
+
+        /**
+         * Sets the destination port for the visual voicemail SMS filter.
+         *
+         * @param destinationPort The destination port, or {@link #DESTINATION_PORT_ANY}, or {@link
+         * #DESTINATION_PORT_DATA_SMS}
+         */
+        public Builder setDestinationPort(int destinationPort) {
+            mDestinationPort = destinationPort;
+            return this;
+        }
+
+    }
+
+    /**
+     * The client prefix for the visual voicemail SMS filter. The client prefix will appear at the
+     * start of a visual voicemail SMS message, followed by a colon(:).
+     */
+    public final String clientPrefix;
+
+    /**
+     * The originating number whitelist for the visual voicemail SMS filter of a phone account. If
+     * the list is not null only the SMS messages from a number in the list can be considered as a
+     * visual voicemail SMS. Otherwise, messages from any address will be considered.
+     */
+    public final List<String> originatingNumbers;
+
+    /**
+     * The destination port for the visual voicemail SMS filter, or {@link #DESTINATION_PORT_ANY},
+     * or {@link #DESTINATION_PORT_DATA_SMS}
+     */
+    public final int destinationPort;
+
+    /**
+     * Use {@link Builder} to construct
+     */
+    private VisualVoicemailSmsFilterSettings(Builder builder) {
+        clientPrefix = builder.mClientPrefix;
+        originatingNumbers = builder.mOriginatingNumbers;
+        destinationPort = builder.mDestinationPort;
+    }
+
+    public static final Creator<VisualVoicemailSmsFilterSettings> CREATOR =
+            new Creator<VisualVoicemailSmsFilterSettings>() {
+                @Override
+                public VisualVoicemailSmsFilterSettings createFromParcel(Parcel in) {
+                    Builder builder = new Builder();
+                    builder.setClientPrefix(in.readString());
+                    builder.setOriginatingNumbers(in.createStringArrayList());
+                    builder.setDestinationPort(in.readInt());
+
+                    return builder.build();
+                }
+
+                @Override
+                public VisualVoicemailSmsFilterSettings[] newArray(int size) {
+                    return new VisualVoicemailSmsFilterSettings[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(clientPrefix);
+        dest.writeStringList(originatingNumbers);
+        dest.writeInt(destinationPort);
+    }
+
+}
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
index a9614a6..406d22d 100644
--- a/telephony/java/com/android/ims/internal/IImsService.aidl
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -38,8 +38,19 @@
     void close(int serviceId);
     boolean isConnected(int serviceId, int serviceType, int callType);
     boolean isOpened(int serviceId);
+
+    /**
+     * Replace existing registration listener
+     *
+     */
     void setRegistrationListener(int serviceId, in IImsRegistrationListener listener);
 
+    /**
+     * Add new registration listener
+     */
+    void addRegistrationListener(int phoneId, int serviceClass,
+            in IImsRegistrationListener listener);
+
     ImsCallProfile createCallProfile(int serviceId, int serviceType, int callType);
 
     IImsCallSession createCallSession(int serviceId, in ImsCallProfile profile,
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index e4981ce..8166e00 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -104,6 +104,7 @@
     public static final int CMD_CLEAR_PROVISIONING_SPINNER = BASE + 42;
     public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 43;
     public static final int EVENT_REDIRECTION_DETECTED = BASE + 44;
+    public static final int EVENT_PCO_DATA_RECEIVED = BASE + 45;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index c61ed2a..f6aef08 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -118,17 +118,6 @@
     int setDisplayName(String displayName, int subId);
 
     /**
-     * Set Sim Provisioning Status by subscription ID
-     * @param simProvisionStatus with the subscription:
-     * {@See SubscriptionManager#SIM_PROVISIONED}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_COLD}
-     * {@See SubscriptionManager#SIM_UNPROVISIONED_OUT_OF_CREDIT}
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    int setSimProvisioningStatus(int simProvisioningStatus, int subId);
-
-    /**
      * Set display name by simInfo index with name source
      * @param displayName the display name of SIM card
      * @param subId the unique SubscriptionInfo index in database
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index bb8aaad5..2171c9e 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.net.Uri;
+import android.service.carrier.CarrierIdentifier;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CellInfo;
@@ -28,8 +29,11 @@
 import android.telephony.NeighboringCellInfo;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyHistogram;
+import android.telephony.VisualVoicemailSmsFilterSettings;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.OperatorInfo;
+
 import java.util.List;
 
 
@@ -450,6 +454,20 @@
      */
     int getVoiceMessageCountForSubscriber(int subId);
 
+    // Not oneway, caller needs to make sure the vaule is set before receiving a SMS
+    void enableVisualVoicemailSmsFilter(String callingPackage, int subId,
+            in VisualVoicemailSmsFilterSettings settings);
+
+    oneway void disableVisualVoicemailSmsFilter(String callingPackage, int subId);
+
+    // Get settings set by the calling package
+    VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(String callingPackage,
+            int subId);
+
+    // Get settings set by the package, requires READ_PRIVILEGED_PHONE_STATE permission
+    VisualVoicemailSmsFilterSettings getSystemVisualVoicemailSmsFilterSettings(String packageName,
+            int subId);
+
     /**
      * Returns the network type for data transmission
      * Legacy call, permission-free
@@ -1067,4 +1085,69 @@
      * Returns a list of packages that have carrier privileges.
      */
     List<String> getPackagesWithCarrierPrivileges();
+
+    /**
+     * Return the application ID for the app type.
+     *
+     * @param subId the subscription ID that this request applies to.
+     * @param appType the uicc app type,
+     * @return Application ID for specificied app type or null if no uicc or error.
+     */
+    String getAidForAppType(int subId, int appType);
+
+    /**
+    * Return the Electronic Serial Number.
+    *
+    * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+    *
+    * @param subId the subscription ID that this request applies to.
+    * @return ESN or null if error.
+    * @hide
+    */
+    String getEsn(int subId);
+
+    /**
+     * Get snapshot of Telephony histograms
+     * @return List of Telephony histograms
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges.
+     */
+    List<TelephonyHistogram> getTelephonyHistograms();
+
+    /**
+     * Set the allowed carrier list for slotId
+     * Require system privileges. In the future we may add this to carrier APIs.
+     *
+     * @return The number of carriers set successfully. Should match length of
+     * carriers on success.
+     */
+    int setAllowedCarriers(int slotId, in List<CarrierIdentifier> carriers);
+
+    /**
+     * Get the allowed carrier list for slotId.
+     * Require system privileges. In the future we may add this to carrier APIs.
+     *
+     * @return List of {@link android.service.carrier.CarrierIdentifier}; empty list
+     * means all carriers are allowed.
+     */
+    List<CarrierIdentifier> getAllowedCarriers(int slotId);
+
+   /**
+    * Action set from carrier signalling broadcast receivers to enable/disable metered apns
+    * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+    * @param subId the subscription ID that this action applies to.
+    * @param enabled control enable or disable metered apns.
+    * @hide
+    */
+    void carrierActionSetMeteredApnsEnabled(int subId, boolean visible);
+
+    /**
+     * Action set from carrier signalling broadcast receivers to enable/disable radio
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * @param subId the subscription ID that this action applies to.
+     * @param enabled control enable or disable radio.
+     * @hide
+     */
+    void carrierActionSetRadioEnabled(int subId, boolean enabled);
 }
diff --git a/telephony/java/com/android/internal/telephony/IccCardConstants.java b/telephony/java/com/android/internal/telephony/IccCardConstants.java
index c1e2518..f3d9335 100644
--- a/telephony/java/com/android/internal/telephony/IccCardConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccCardConstants.java
@@ -32,6 +32,8 @@
     public static final String INTENT_VALUE_ICC_ABSENT = "ABSENT";
     /* CARD_IO_ERROR means for three consecutive times there was SIM IO error */
     static public final String INTENT_VALUE_ICC_CARD_IO_ERROR = "CARD_IO_ERROR";
+    /* CARD_RESTRICTED means card is present but not usable due to carrier restrictions */
+    static public final String INTENT_VALUE_ICC_CARD_RESTRICTED = "CARD_RESTRICTED";
     /* LOCKED means ICC is locked by pin or by network */
     public static final String INTENT_VALUE_ICC_LOCKED = "LOCKED";
     //TODO: we can remove this state in the future if Bug 18489776 analysis
@@ -74,7 +76,8 @@
         READY,          /** ordinal(5) == {@See TelephonyManager#SIM_STATE_READY} */
         NOT_READY,      /** ordinal(6) == {@See TelephonyManager#SIM_STATE_NOT_READY} */
         PERM_DISABLED,  /** ordinal(7) == {@See TelephonyManager#SIM_STATE_PERM_DISABLED} */
-        CARD_IO_ERROR;  /** ordinal(8) == {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR} */
+        CARD_IO_ERROR,  /** ordinal(8) == {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR} */
+        CARD_RESTRICTED;/** ordinal(9) == {@See TelephonyManager#SIM_STATE_CARD_RESTRICTED} */
 
         public boolean isPinLocked() {
             return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
@@ -83,7 +86,8 @@
         public boolean iccCardExist() {
             return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED)
                     || (this == NETWORK_LOCKED) || (this == READY)
-                    || (this == PERM_DISABLED) || (this == CARD_IO_ERROR));
+                    || (this == PERM_DISABLED) || (this == CARD_IO_ERROR)
+                    || (this == CARD_RESTRICTED));
         }
 
         public static State intToState(int state) throws IllegalArgumentException {
@@ -97,6 +101,7 @@
                 case 6: return NOT_READY;
                 case 7: return PERM_DISABLED;
                 case 8: return CARD_IO_ERROR;
+                case 9: return CARD_RESTRICTED;
                 default:
                     throw new IllegalArgumentException();
             }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index cfc5305..a91e9be 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -409,6 +409,8 @@
     int RIL_REQUEST_STOP_LCE = 133;
     int RIL_REQUEST_PULL_LCEDATA = 134;
     int RIL_REQUEST_GET_ACTIVITY_INFO = 135;
+    int RIL_REQUEST_SET_ALLOWED_CARRIERS = 136;
+    int RIL_REQUEST_GET_ALLOWED_CARRIERS = 137;
 
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
 
@@ -459,4 +461,5 @@
     int RIL_UNSOL_ON_SS = 1043;
     int RIL_UNSOL_STK_CC_ALPHA_NOTIFY = 1044;
     int RIL_UNSOL_LCEDATA_RECV = 1045;
+    int RIL_UNSOL_PCO_DATA = 1046;
 }
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 46b0fbd..0168874 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -402,34 +402,59 @@
     /**
      * <p>Broadcast Action: when data connections get redirected with validation failure.
      * intended for sim/account status checks and only sent to the specified carrier app
-     * feedback is via carrier/system APIs to report cold-sim, out-of-credit-sim, etc
      * The intent will have the following extra values:</p>
      * <ul>
-     *   <li>redirectUrl</li><dd>A string with the redirection url info.</dd>
-     *   <li>subId</li><dd>Sub Id which associated the data redirection.</dd>
+     *   <li>apnType</li><dd>A string with the apn type.</dd>
+     *   <li>redirectionUrl</li><dd>redirection url string</dd>
+     *   <li>subId</dt><li>Sub Id which associated the data connection failure.</dd>
      * </ul>
      * <p class="note">This is a protected intent that can only be sent by the system.</p>
      */
-    public static final String ACTION_DATA_CONNECTION_REDIRECTED =
-            "android.intent.action.REDIRECTION_DETECTED";
+    public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
+            "android.intent.action.CARRIER_SIGNAL_REDIRECTED";
     /**
      * <p>Broadcast Action: when data connections setup fails.
      * intended for sim/account status checks and only sent to the specified carrier app
-     * feedback is via carrier/system APIs to report cold-sim, out-of-credit-sim, etc
      * The intent will have the following extra values:</p>
      * <ul>
      *   <li>apnType</li><dd>A string with the apn type.</dd>
      *   <li>errorCode</li><dd>A integer with dataFailCause.</dd>
-     *   <li>subId</dt><li>Sub Id which associated the data redirection.</dd>
+     *   <li>subId</dt><li>Sub Id which associated the data connection failure.</dd>
      * </ul>
      * <p class="note">This is a protected intent that can only be sent by the system. </p>
      */
-    public static final String ACTION_REQUEST_NETWORK_FAILED =
-            "android.intent.action.REQUEST_NETWORK_FAILED";
+    public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
+            "android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
 
     /**
-     * Broadcast action to trigger CI OMA-DM Session.
+     * <p>Broadcast Action: when pco value is available.
+     * intended for sim/account status checks and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>apnType</li><dd>A string with the apn type.</dd>
+     *   <li>apnProto</li><dd>A string with the protocol of the apn connection (IP,IPV6,
+     *                        IPV4V6)</dd>
+     *   <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd>
+     *   <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd>
+     *   <li>subId</dt><li>Sub Id which associated the data connection.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
      */
+    public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
+            "android.intent.action.CARRIER_SIGNAL_PCO_VALUE";
+
+    // CARRIER_SIGNAL_ACTION extra keys
+    public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
+    public static final String EXTRA_ERROR_CODE_KEY = "errorCode";
+    public static final String EXTRA_APN_TYPE_KEY = "apnType";
+    public static final String EXTRA_APN_PROTO_KEY = "apnProto";
+    public static final String EXTRA_PCO_ID_KEY = "pcoId";
+    public static final String EXTRA_PCO_VALUE_KEY = "pcoValue";
+
+
+   /**
+     * Broadcast action to trigger CI OMA-DM Session.
+    */
     public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
             "com.android.omadm.service.CONFIGURATION_UPDATE";
 }
diff --git a/tests/WallpaperTest/res/values/strings.xml b/tests/WallpaperTest/res/values/strings.xml
index fd21259..80a3d49 100644
--- a/tests/WallpaperTest/res/values/strings.xml
+++ b/tests/WallpaperTest/res/values/strings.xml
@@ -24,6 +24,9 @@
         Test wallpaper for use with the wallpaper test app.
     </string>
 
+    <string name="test_wallpaper_context_uri">https://www.google.com/maps/@37.8092876,-122.408986,1391m/data=!3m1!1e3</string>
+    <string name="test_wallpaper_context_description">Explore</string>
+
     <string name="dimens">Dimens: </string>
     <string name="width">Width: </string>
     <string name="height">Height: </string>
diff --git a/tests/WallpaperTest/res/xml/test_wallpaper.xml b/tests/WallpaperTest/res/xml/test_wallpaper.xml
index 9f7d714b..ba22478 100644
--- a/tests/WallpaperTest/res/xml/test_wallpaper.xml
+++ b/tests/WallpaperTest/res/xml/test_wallpaper.xml
@@ -23,4 +23,7 @@
 <wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
     android:author="@string/test_wallpaper_author"
     android:description="@string/test_wallpaper_desc"
-    android:thumbnail="@drawable/test_wallpaper_thumb" />
+    android:thumbnail="@drawable/test_wallpaper_thumb"
+    android:showMetadataInPreview="true"
+    android:contextUri="@string/test_wallpaper_context_uri"
+    android:contextDescription="@string/test_wallpaper_context_description"/>
diff --git a/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java b/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java
index 95db6d1..ab36c22 100644
--- a/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java
+++ b/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java
@@ -144,6 +144,14 @@
         @Override
         public void onSurfaceCreated(SurfaceHolder holder) {
             super.onSurfaceCreated(holder);
+
+            // Simulate some slowness, so we can test the loading process in the live wallpaper
+            // picker.
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
         }
 
         @Override
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
new file mode 100644
index 0000000..d53167f
--- /dev/null
+++ b/tests/utils/testutils/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := frameworks-base-testutils
+LOCAL_MODULE_TAG := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under,java)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-target
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java b/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java
new file mode 100644
index 0000000..746c77d
--- /dev/null
+++ b/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.app.test;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * Utilities for creating Answers for mock objects
+ */
+public class MockAnswerUtil {
+
+    /**
+     * Answer that calls the method in the Answer called "answer" that matches the type signature of
+     * the method being answered. An error will be thrown at runtime if the signature does not match
+     * exactly.
+     */
+    public static class AnswerWithArguments implements Answer<Object> {
+        @Override
+        public final Object answer(InvocationOnMock invocation) throws Throwable {
+            Method method = invocation.getMethod();
+            try {
+                Method implementation = getClass().getMethod("answer", method.getParameterTypes());
+                if (!implementation.getReturnType().equals(method.getReturnType())) {
+                    throw new RuntimeException("Found answer method does not have expected return "
+                            + "type. Expected: " + method.getReturnType() + ", got "
+                            + implementation.getReturnType());
+                }
+                Object[] args = invocation.getArguments();
+                try {
+                    return implementation.invoke(this, args);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException("Error invoking answer method", e);
+                } catch (InvocationTargetException e) {
+                    throw e.getCause();
+                }
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException("Could not find answer method with the expected args "
+                        + Arrays.toString(method.getParameterTypes()), e);
+            }
+        }
+    }
+
+}
diff --git a/tests/utils/testutils/java/android/app/test/TestAlarmManager.java b/tests/utils/testutils/java/android/app/test/TestAlarmManager.java
new file mode 100644
index 0000000..e90ea1e
--- /dev/null
+++ b/tests/utils/testutils/java/android/app/test/TestAlarmManager.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 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 android.app.test;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import android.app.AlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.os.Handler;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Creates an AlarmManager whose alarm dispatch can be controlled
+ * Currently only supports alarm listeners
+ *
+ * Alarm listeners will be dispatched to the handler provided or will
+ * be dispatched immediately if they would have been sent to the main
+ * looper (handler was null).
+ */
+public class TestAlarmManager {
+    private final AlarmManager mAlarmManager;
+    private final List<PendingAlarm> mPendingAlarms;
+
+    public TestAlarmManager() throws Exception {
+        mPendingAlarms = new ArrayList<>();
+
+        mAlarmManager = mock(AlarmManager.class);
+        doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
+                any(AlarmManager.OnAlarmListener.class), any(Handler.class));
+        doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
+                anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
+        doAnswer(new CancelListenerAnswer())
+                .when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
+    }
+
+    public AlarmManager getAlarmManager() {
+        return mAlarmManager;
+    }
+
+    /**
+     * Dispatch a pending alarm with the given tag
+     * @return if any alarm was dispatched
+     */
+    public boolean dispatch(String tag) {
+        for (int i = 0; i < mPendingAlarms.size(); ++i) {
+            PendingAlarm alarm = mPendingAlarms.get(i);
+            if (Objects.equals(tag, alarm.getTag())) {
+                mPendingAlarms.remove(i);
+                alarm.dispatch();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return if an alarm with the given tag is pending
+     */
+    public boolean isPending(String tag) {
+        for (int i = 0; i < mPendingAlarms.size(); ++i) {
+            PendingAlarm alarm = mPendingAlarms.get(i);
+            if (Objects.equals(tag, alarm.getTag())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return trigger time of an pending alarm with the given tag
+     *         -1 if no pending alarm with the given tag
+     */
+    public long getTriggerTimeMillis(String tag) {
+        for (int i = 0; i < mPendingAlarms.size(); ++i) {
+            PendingAlarm alarm = mPendingAlarms.get(i);
+            if (Objects.equals(tag, alarm.getTag())) {
+                return alarm.getTriggerTimeMillis();
+            }
+        }
+        return -1;
+    }
+
+    private static class PendingAlarm {
+        private final int mType;
+        private final long mTriggerAtMillis;
+        private final String mTag;
+        private final Runnable mCallback;
+
+        public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
+            mType = type;
+            mTriggerAtMillis = triggerAtMillis;
+            mTag = tag;
+            mCallback = callback;
+        }
+
+        public void dispatch() {
+            if (mCallback != null) {
+                mCallback.run();
+            }
+        }
+
+        public Runnable getCallback() {
+            return mCallback;
+        }
+
+        public String getTag() {
+            return mTag;
+        }
+
+        public long getTriggerTimeMillis() {
+            return mTriggerAtMillis;
+        }
+    }
+
+    private class SetListenerAnswer extends AnswerWithArguments {
+        public void answer(int type, long triggerAtMillis, String tag,
+                AlarmManager.OnAlarmListener listener, Handler handler) {
+            mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
+                            new AlarmListenerRunnable(listener, handler)));
+        }
+    }
+
+    private class CancelListenerAnswer extends AnswerWithArguments {
+        public void answer(AlarmManager.OnAlarmListener listener) {
+            Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
+            while (alarmItr.hasNext()) {
+                PendingAlarm alarm = alarmItr.next();
+                if (alarm.getCallback() instanceof AlarmListenerRunnable) {
+                    AlarmListenerRunnable alarmCallback =
+                            (AlarmListenerRunnable) alarm.getCallback();
+                    if (alarmCallback.getListener() == listener) {
+                        alarmItr.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    private static class AlarmListenerRunnable implements Runnable {
+        private final AlarmManager.OnAlarmListener mListener;
+        private final Handler mHandler;
+        public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = handler;
+        }
+
+        public AlarmManager.OnAlarmListener getListener() {
+            return mListener;
+        }
+
+        @Override
+        public void run() {
+            if (mHandler != null) {
+                mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mListener.onAlarm();
+                        }
+                    });
+            } else { // normally gets dispatched in main looper
+                mListener.onAlarm();
+            }
+        }
+    }
+}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
new file mode 100644
index 0000000..e8ceb4a
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2015 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 android.os.test;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Creates a looper whose message queue can be manipulated
+ * This allows testing code that uses a looper to dispatch messages in a deterministic manner
+ * Creating a TestLooper will also install it as the looper for the current thread
+ */
+public class TestLooper {
+    protected final Looper mLooper;
+
+    private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
+    private static final Field THREAD_LOCAL_LOOPER_FIELD;
+    private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
+    private static final Field MESSAGE_NEXT_FIELD;
+    private static final Field MESSAGE_WHEN_FIELD;
+    private static final Method MESSAGE_MARK_IN_USE_METHOD;
+    private static final String TAG = "TestLooper";
+
+    private AutoDispatchThread mAutoDispatchThread;
+
+    static {
+        try {
+            LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
+            LOOPER_CONSTRUCTOR.setAccessible(true);
+            THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
+            THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
+            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+            MESSAGE_NEXT_FIELD.setAccessible(true);
+            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+            MESSAGE_WHEN_FIELD.setAccessible(true);
+            MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
+            MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
+        } catch (NoSuchFieldException | NoSuchMethodException e) {
+            throw new RuntimeException("Failed to initialize TestLooper", e);
+        }
+    }
+
+
+    public TestLooper() {
+        try {
+            mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
+
+            ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
+                    .get(null);
+            threadLocalLooper.set(mLooper);
+        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
+            throw new RuntimeException("Reflection error constructing or accessing looper", e);
+        }
+    }
+
+    public Looper getLooper() {
+        return mLooper;
+    }
+
+    private Message getMessageLinkedList() {
+        try {
+            MessageQueue queue = mLooper.getQueue();
+            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
+                    e);
+        }
+    }
+
+    public void moveTimeForward(long milliSeconds) {
+        try {
+            Message msg = getMessageLinkedList();
+            while (msg != null) {
+                long updatedWhen = msg.getWhen() - milliSeconds;
+                if (updatedWhen < 0) {
+                    updatedWhen = 0;
+                }
+                MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
+                msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+            }
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
+        }
+    }
+
+    private Message messageQueueNext() {
+        try {
+            long now = SystemClock.uptimeMillis();
+
+            Message prevMsg = null;
+            Message msg = getMessageLinkedList();
+            if (msg != null && msg.getTarget() == null) {
+                // Stalled by a barrier. Find the next asynchronous message in
+                // the queue.
+                do {
+                    prevMsg = msg;
+                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+                } while (msg != null && !msg.isAsynchronous());
+            }
+            if (msg != null) {
+                if (now >= msg.getWhen()) {
+                    // Got a message.
+                    if (prevMsg != null) {
+                        MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
+                    } else {
+                        MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
+                                MESSAGE_NEXT_FIELD.get(msg));
+                    }
+                    MESSAGE_NEXT_FIELD.set(msg, null);
+                    MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
+                    return msg;
+                }
+            }
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException("Access failed in TestLooper", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * @return true if there are pending messages in the message queue
+     */
+    public synchronized boolean isIdle() {
+        Message messageList = getMessageLinkedList();
+
+        return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
+    }
+
+    /**
+     * @return the next message in the Looper's message queue or null if there is none
+     */
+    public synchronized Message nextMessage() {
+        if (isIdle()) {
+            return messageQueueNext();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Dispatch the next message in the queue
+     * Asserts that there is a message in the queue
+     */
+    public synchronized void dispatchNext() {
+        assertTrue(isIdle());
+        Message msg = messageQueueNext();
+        if (msg == null) {
+            return;
+        }
+        msg.getTarget().dispatchMessage(msg);
+    }
+
+    /**
+     * Dispatch all messages currently in the queue
+     * Will not fail if there are no messages pending
+     * @return the number of messages dispatched
+     */
+    public synchronized int dispatchAll() {
+        int count = 0;
+        while (isIdle()) {
+            dispatchNext();
+            ++count;
+        }
+        return count;
+    }
+
+    /**
+     * Thread used to dispatch messages when the main thread is blocked waiting for a response.
+     */
+    private class AutoDispatchThread extends Thread {
+        private static final int MAX_LOOPS = 100;
+        private static final int LOOP_SLEEP_TIME_MS = 10;
+
+        private RuntimeException mAutoDispatchException = null;
+
+        /**
+         * Run method for the auto dispatch thread.
+         * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
+         * The thread continues looping and attempting to dispatch all messages until at
+         * least one message has been dispatched.
+         */
+        @Override
+        public void run() {
+            int dispatchCount = 0;
+            for (int i = 0; i < MAX_LOOPS; i++) {
+                try {
+                    dispatchCount = dispatchAll();
+                } catch (RuntimeException e) {
+                    mAutoDispatchException = e;
+                }
+                Log.d(TAG, "dispatched " + dispatchCount + " messages");
+                if (dispatchCount > 0) {
+                    return;
+                }
+                try {
+                    Thread.sleep(LOOP_SLEEP_TIME_MS);
+                } catch (InterruptedException e) {
+                    mAutoDispatchException = new IllegalStateException(
+                            "stopAutoDispatch called before any messages were dispatched.");
+                    return;
+                }
+            }
+            Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
+            mAutoDispatchException = new IllegalStateException(
+                    "TestLooper did not dispatch any messages before exiting.");
+        }
+
+        /**
+         * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed
+         * to the main thread.
+         *
+         * @return RuntimeException Exception created by stopping without dispatching a message
+         */
+        public RuntimeException getException() {
+            return mAutoDispatchException;
+        }
+    }
+
+    /**
+     * Create and start a new AutoDispatchThread if one is not already running.
+     */
+    public void startAutoDispatch() {
+        if (mAutoDispatchThread != null) {
+            throw new IllegalStateException(
+                    "startAutoDispatch called with the AutoDispatchThread already running.");
+        }
+        mAutoDispatchThread = new AutoDispatchThread();
+        mAutoDispatchThread.start();
+    }
+
+    /**
+     * If an AutoDispatchThread is currently running, stop and clean up.
+     */
+    public void stopAutoDispatch() {
+        if (mAutoDispatchThread != null) {
+            if (mAutoDispatchThread.isAlive()) {
+                mAutoDispatchThread.interrupt();
+            }
+            try {
+                mAutoDispatchThread.join();
+            } catch (InterruptedException e) {
+                // Catch exception from join.
+            }
+
+            RuntimeException e = mAutoDispatchThread.getException();
+            mAutoDispatchThread = null;
+            if (e != null) {
+                throw e;
+            }
+        } else {
+            // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
+            throw new IllegalStateException(
+                    "stopAutoDispatch called without startAutoDispatch.");
+        }
+    }
+}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooperTest.java b/tests/utils/testutils/java/android/os/test/TestLooperTest.java
new file mode 100644
index 0000000..40d83b5
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/TestLooperTest.java
@@ -0,0 +1,371 @@
+/*
+ * 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 android.os.test;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test TestLooperAbstractTime which provides control over "time". Note that
+ * real-time is being used as well. Therefore small time increments are NOT
+ * reliable. All tests are in "K" units (i.e. *1000).
+ */
+
+@SmallTest
+public class TestLooperTest {
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private Handler mHandlerSpy;
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTestLooper = new TestLooper();
+        mHandler = new Handler(mTestLooper.getLooper());
+        mHandlerSpy = spy(mHandler);
+    }
+
+    /**
+     * Basic test with no time stamps: dispatch 4 messages, check that all 4
+     * delivered (in correct order).
+     */
+    @Test
+    public void testNoTimeMovement() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageC));
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageC", messageC, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, A@10K. Don't move time.
+     * <p>
+     * Expected: only get A, B
+     */
+    @Test
+    public void testDelayedDispatchNoTimeMove() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, A@10K, Advance time by 5K.
+     * <p>
+     * Expected: only get A, B, C
+     */
+    @Test
+    public void testDelayedDispatchAdvanceTimeOnce() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
+        mTestLooper.moveTimeForward(5000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, Advance time by 4K, A@1K, B@2K Advance
+     * time by 1K.
+     * <p>
+     * Expected: get A, B, C, A
+     */
+    @Test
+    public void testDelayedDispatchAdvanceTimeTwice() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mTestLooper.moveTimeForward(4000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 1000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+        mTestLooper.moveTimeForward(1000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageA", messageA, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, Advance time by 4K, A@5K, B@2K Advance
+     * time by 3K.
+     * <p>
+     * Expected: get A, B, C, B
+     */
+    @Test
+    public void testDelayedDispatchReverseOrder() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mTestLooper.moveTimeForward(4000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+        mTestLooper.moveTimeForward(3000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, Advance time by 4K, dispatch all,
+     * A@5K, B@2K Advance time by 3K, dispatch all.
+     * <p>
+     * Expected: get A, B after first dispatch; then C, B after second dispatch
+     */
+    @Test
+    public void testDelayedDispatchAllMultipleTimes() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mTestLooper.moveTimeForward(4000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+        mTestLooper.moveTimeForward(3000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test AutoDispatch for a single message.
+     * This test would ideally use the Channel sendMessageSynchronously.  At this time, the setup to
+     * get a working test channel is cumbersome.  Until this is fixed, we substitute with a
+     * sendMessage followed by a blocking call.  The main test thread blocks until the test handler
+     * receives the test message (messageA) and sets a boolean true.  Once the boolean is true, the
+     * main thread will exit the busy wait loop, stop autoDispatch and check the assert.
+     *
+     * Enable AutoDispatch, add message, block on message being handled and stop AutoDispatch.
+     * <p>
+     * Expected: handleMessage is called for messageA and stopAutoDispatch is called.
+     */
+    @Test
+    public void testAutoDispatchWithSingleMessage() {
+        final int mLoopSleepTimeMs = 5;
+
+        final int messageA = 1;
+
+        TestLooper mockLooper = new TestLooper();
+        class TestHandler extends Handler {
+            public volatile boolean handledMessage = false;
+            TestHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == messageA) {
+                    handledMessage = true;
+                }
+            }
+        }
+
+        TestHandler testHandler = new TestHandler(mockLooper.getLooper());
+        mockLooper.startAutoDispatch();
+        testHandler.sendMessage(testHandler.obtainMessage(messageA));
+        while (!testHandler.handledMessage) {
+            // Block until message is handled
+            try {
+                Thread.sleep(mLoopSleepTimeMs);
+            } catch (InterruptedException e) {
+                // Interrupted while sleeping.
+            }
+        }
+        mockLooper.stopAutoDispatch();
+        assertTrue("TestHandler should have received messageA", testHandler.handledMessage);
+    }
+
+    /**
+     * Test starting AutoDispatch while already running throws IllegalStateException
+     * Enable AutoDispatch two times in a row.
+     * <p>
+     * Expected: catch IllegalStateException on second call.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testRepeatedStartAutoDispatchThrowsException() {
+        mTestLooper.startAutoDispatch();
+        mTestLooper.startAutoDispatch();
+    }
+
+    /**
+     * Test stopping AutoDispatch without previously starting throws IllegalStateException
+     * Stop AutoDispatch
+     * <p>
+     * Expected: catch IllegalStateException on second call.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testStopAutoDispatchWithoutStartThrowsException() {
+        mTestLooper.stopAutoDispatch();
+    }
+
+    /**
+     * Test AutoDispatch exits and does not dispatch a later message.
+     * Start and stop AutoDispatch then add a message.
+     * <p>
+     * Expected: After AutoDispatch is stopped, dispatchAll will return 1.
+     */
+    @Test
+    public void testAutoDispatchStopsCleanlyWithoutDispatchingAMessage() {
+        final int messageA = 1;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mTestLooper.startAutoDispatch();
+        try {
+            mTestLooper.stopAutoDispatch();
+        } catch (IllegalStateException e) {
+            //  Stopping without a dispatch will throw an exception.
+        }
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        assertEquals("One message should be dispatched", 1, mTestLooper.dispatchAll());
+    }
+
+    /**
+     * Test AutoDispatch throws an exception when no messages are dispatched.
+     * Start and stop AutoDispatch
+     * <p>
+     * Expected: Exception is thrown with the stopAutoDispatch call.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testAutoDispatchThrowsExceptionWhenNoMessagesDispatched() {
+        mTestLooper.startAutoDispatch();
+        mTestLooper.stopAutoDispatch();
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java
new file mode 100644
index 0000000..25cd5b9
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java
@@ -0,0 +1,96 @@
+/*
+ * 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.internal.util.test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+
+
+/**
+ * Provides an AsyncChannel interface that implements the connection initiating half of a
+ * bidirectional channel as described in {@link com.android.internal.util.AsyncChannel}.
+ */
+public class BidirectionalAsyncChannel {
+    private static final String TAG = "BidirectionalAsyncChannel";
+
+    private AsyncChannel mChannel;
+    public enum ChannelState { DISCONNECTED, HALF_CONNECTED, CONNECTED, FAILURE };
+    private ChannelState mState = ChannelState.DISCONNECTED;
+
+    public void assertConnected() {
+        assertEquals("AsyncChannel was not fully connected", ChannelState.CONNECTED, mState);
+    }
+
+    public void connect(final Looper looper, final Messenger messenger,
+            final Handler incomingMessageHandler) {
+        assertEquals("AsyncChannel must be disconnected to connect",
+                ChannelState.DISCONNECTED, mState);
+        mChannel = new AsyncChannel();
+        Handler rawMessageHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                            Log.d(TAG, "Successfully half connected " + this);
+                            mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                            mState = ChannelState.HALF_CONNECTED;
+                        } else {
+                            Log.d(TAG, "Failed to connect channel " + this);
+                            mState = ChannelState.FAILURE;
+                            mChannel = null;
+                        }
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+                        mState = ChannelState.CONNECTED;
+                        Log.d(TAG, "Channel fully connected" + this);
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                        mState = ChannelState.DISCONNECTED;
+                        mChannel = null;
+                        Log.d(TAG, "Channel disconnected" + this);
+                        break;
+                    default:
+                        incomingMessageHandler.handleMessage(msg);
+                        break;
+                    }
+                }
+            };
+        mChannel.connect(null, rawMessageHandler, messenger);
+    }
+
+    public void disconnect() {
+        assertEquals("AsyncChannel must be connected to disconnect",
+                ChannelState.CONNECTED, mState);
+        mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_DISCONNECT);
+        mState = ChannelState.DISCONNECTED;
+        mChannel = null;
+    }
+
+    public void sendMessage(Message msg) {
+        assertEquals("AsyncChannel must be connected to send messages",
+                ChannelState.CONNECTED, mState);
+        mChannel.sendMessage(msg);
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java
new file mode 100644
index 0000000..49c8332
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.util.test;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides an interface for the server side implementation of a bidirectional channel as described
+ * in {@link com.android.internal.util.AsyncChannel}.
+ */
+public class BidirectionalAsyncChannelServer {
+
+    private static final String TAG = "BidirectionalAsyncChannelServer";
+
+    // Keeps track of incoming clients, which are identifiable by their messengers.
+    private final Map<Messenger, AsyncChannel> mClients = new HashMap<>();
+
+    private Messenger mMessenger;
+
+    public BidirectionalAsyncChannelServer(final Context context, final Looper looper,
+            final Handler messageHandler) {
+        Handler handler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                AsyncChannel channel = mClients.get(msg.replyTo);
+                switch (msg.what) {
+                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+                        if (channel != null) {
+                            Log.d(TAG, "duplicate client connection: " + msg.sendingUid);
+                            channel.replyToMessage(msg,
+                                    AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                    AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
+                        } else {
+                            channel = new AsyncChannel();
+                            mClients.put(msg.replyTo, channel);
+                            channel.connected(context, this, msg.replyTo);
+                            channel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                    AsyncChannel.STATUS_SUCCESSFUL);
+                        }
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECT:
+                        channel.disconnect();
+                        break;
+
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                        mClients.remove(msg.replyTo);
+                        break;
+
+                    default:
+                        messageHandler.handleMessage(msg);
+                        break;
+                }
+            }
+        };
+        mMessenger = new Messenger(handler);
+    }
+
+    public Messenger getMessenger() {
+        return mMessenger;
+    }
+
+}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 3a1e2bb..4b5ea65 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -115,6 +115,7 @@
 
 toolSources := \
 	compile/Compile.cpp \
+	diff/Diff.cpp \
 	dump/Dump.cpp \
 	link/Link.cpp
 
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index a2fadd9..00d8aae 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -24,6 +24,7 @@
 extern int compile(const std::vector<StringPiece>& args);
 extern int link(const std::vector<StringPiece>& args);
 extern int dump(const std::vector<StringPiece>& args);
+extern int diff(const std::vector<StringPiece>& args);
 
 } // namespace aapt
 
@@ -44,12 +45,14 @@
             return aapt::link(args);
         } else if (command == "dump" || command == "d") {
             return aapt::dump(args);
+        } else if (command == "diff") {
+            return aapt::diff(args);
         }
         std::cerr << "unknown command '" << command << "'\n";
     } else {
         std::cerr << "no command specified\n";
     }
 
-    std::cerr << "\nusage: aapt2 [compile|link|dump] ..." << std::endl;
+    std::cerr << "\nusage: aapt2 [compile|link|dump|diff] ..." << std::endl;
     return 1;
 }
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9704d970..a84c306 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -152,7 +152,7 @@
                 break;
             }
 
-            spanStack.back().lastChar = builder.str().size();
+            spanStack.back().lastChar = builder.str().size() - 1;
             outStyleString->spans.push_back(spanStack.back());
             spanStack.pop_back();
 
@@ -1058,6 +1058,16 @@
 
     std::unique_ptr<Array> array = util::make_unique<Array>();
 
+    bool translateable = mOptions.translatable;
+    if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
+        if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
+            mDiag->error(DiagMessage(outResource->source)
+                         << "invalid value for 'translatable'. Must be a boolean");
+            return false;
+        }
+    }
+    array->setTranslateable(translateable);
+
     bool error = false;
     const size_t depth = parser->getDepth();
     while (xml::XmlPullParser::nextChildNode(parser, depth)) {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 8d734f3..e700ed9 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -189,6 +189,17 @@
     return results;
 }
 
+std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
+        const std::function<bool(ResourceConfigValue*)>& f) {
+    std::vector<ResourceConfigValue*> results;
+    for (auto& configValue : values) {
+        if (f(configValue.get())) {
+            results.push_back(configValue.get());
+        }
+    }
+    return results;
+}
+
 /**
  * The default handler for collisions. A return value of -1 means keep the
  * existing value, 0 means fail, and +1 means take the incoming value.
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 7f5c2b8..5690ea6 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -26,6 +26,7 @@
 #include "io/File.h"
 
 #include <android-base/macros.h>
+#include <functional>
 #include <map>
 #include <memory>
 #include <string>
@@ -109,6 +110,9 @@
     ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
                                            const StringPiece& product);
     std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
+    std::vector<ResourceConfigValue*> findValuesIf(
+            const std::function<bool(ResourceConfigValue*)>& f);
+
 
 private:
     DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 74c48b0..a0a7efc 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -289,7 +289,7 @@
 std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
                                                     const StringPiece16& str) {
     android::Res_value flags = { };
-    flags.dataType = android::Res_value::TYPE_INT_DEC;
+    flags.dataType = android::Res_value::TYPE_INT_HEX;
     flags.data = 0u;
 
     if (util::trimWhitespace(str).empty()) {
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index dd7ff01..c10b134 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -39,6 +39,14 @@
 RawString::RawString(const StringPool::Ref& ref) : value(ref) {
 }
 
+bool RawString::equals(const Value* value) const {
+    const RawString* other = valueCast<RawString>(value);
+    if (!other) {
+        return false;
+    }
+    return *this->value == *other->value;
+}
+
 RawString* RawString::clone(StringPool* newPool) const {
     RawString* rs = new RawString(newPool->makeRef(*value));
     rs->mComment = mComment;
@@ -66,6 +74,15 @@
 Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
 }
 
+bool Reference::equals(const Value* value) const {
+    const Reference* other = valueCast<Reference>(value);
+    if (!other) {
+        return false;
+    }
+    return referenceType == other->referenceType && privateReference == other->privateReference &&
+            id == other->id && name == other->name;
+}
+
 bool Reference::flatten(android::Res_value* outValue) const {
     outValue->dataType = (referenceType == Reference::Type::kResource) ?
             android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
@@ -97,6 +114,10 @@
     }
 }
 
+bool Id::equals(const Value* value) const {
+    return valueCast<Id>(value) != nullptr;
+}
+
 bool Id::flatten(android::Res_value* out) const {
     out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
     out->data = util::hostToDevice32(0);
@@ -111,15 +132,15 @@
     *out << "(id)";
 }
 
-String::String(const StringPool::Ref& ref) : value(ref), mTranslateable(true) {
+String::String(const StringPool::Ref& ref) : value(ref) {
 }
 
-void String::setTranslateable(bool val) {
-    mTranslateable = val;
-}
-
-bool String::isTranslateable() const {
-    return mTranslateable;
+bool String::equals(const Value* value) const {
+    const String* other = valueCast<String>(value);
+    if (!other) {
+        return false;
+    }
+    return *this->value == *other->value;
 }
 
 bool String::flatten(android::Res_value* outValue) const {
@@ -144,15 +165,24 @@
     *out << "(string) \"" << *value << "\"";
 }
 
-StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref), mTranslateable(true) {
+StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
 }
 
-void StyledString::setTranslateable(bool val) {
-    mTranslateable = val;
-}
+bool StyledString::equals(const Value* value) const {
+    const StyledString* other = valueCast<StyledString>(value);
+    if (!other) {
+        return false;
+    }
 
-bool StyledString::isTranslateable() const {
-    return mTranslateable;
+    if (*this->value->str == *other->value->str) {
+        const std::vector<StringPool::Span>& spansA = this->value->spans;
+        const std::vector<StringPool::Span>& spansB = other->value->spans;
+        return std::equal(spansA.begin(), spansA.end(), spansB.begin(),
+                          [](const StringPool::Span& a, const StringPool::Span& b) -> bool {
+            return *a.name == *b.name && a.firstChar == b.firstChar && a.lastChar == b.lastChar;
+        });
+    }
+    return false;
 }
 
 bool StyledString::flatten(android::Res_value* outValue) const {
@@ -174,11 +204,22 @@
 
 void StyledString::print(std::ostream* out) const {
     *out << "(styled string) \"" << *value->str << "\"";
+    for (const StringPool::Span& span : value->spans) {
+        *out << " "<< *span.name << ":" << span.firstChar << "," << span.lastChar;
+    }
 }
 
 FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
 }
 
+bool FileReference::equals(const Value* value) const {
+    const FileReference* other = valueCast<FileReference>(value);
+    if (!other) {
+        return false;
+    }
+    return *path == *other->path;
+}
+
 bool FileReference::flatten(android::Res_value* outValue) const {
     if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
         return false;
@@ -209,6 +250,14 @@
     value.data = data;
 }
 
+bool BinaryPrimitive::equals(const Value* value) const {
+    const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value);
+    if (!other) {
+        return false;
+    }
+    return this->value.dataType == other->value.dataType && this->value.data == other->value.data;
+}
+
 bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
     outValue->dataType = value.dataType;
     outValue->data = util::hostToDevice32(value.data);
@@ -228,7 +277,7 @@
             *out << "(integer) " << static_cast<int32_t>(value.data);
             break;
         case android::Res_value::TYPE_INT_HEX:
-            *out << "(integer) " << std::hex << value.data << std::dec;
+            *out << "(integer) 0x" << std::hex << value.data << std::dec;
             break;
         case android::Res_value::TYPE_INT_BOOLEAN:
             *out << "(boolean) " << (value.data != 0 ? "true" : "false");
@@ -253,6 +302,21 @@
     mWeak = w;
 }
 
+bool Attribute::equals(const Value* value) const {
+    const Attribute* other = valueCast<Attribute>(value);
+    if (!other) {
+        return false;
+    }
+
+    return this->typeMask == other->typeMask && this->minInt == other->minInt &&
+            this->maxInt == other->maxInt &&
+            std::equal(this->symbols.begin(), this->symbols.end(),
+                       other->symbols.begin(),
+                       [](const Symbol& a, const Symbol& b) -> bool {
+        return a.symbol.equals(&b.symbol) && a.value == b.value;
+    });
+}
+
 Attribute* Attribute::clone(StringPool* /*newPool*/) const {
     return new Attribute(*this);
 }
@@ -365,6 +429,14 @@
             << "]";
     }
 
+    if (minInt != std::numeric_limits<int32_t>::min()) {
+        *out << " min=" << minInt;
+    }
+
+    if (maxInt != std::numeric_limits<int32_t>::max()) {
+        *out << " max=" << maxInt;
+    }
+
     if (isWeak()) {
         *out << " [weak]";
     }
@@ -445,6 +517,21 @@
     return true;
 }
 
+bool Style::equals(const Value* value) const {
+    const Style* other = valueCast<Style>(value);
+    if (!other) {
+        return false;
+    }
+    if (bool(parent) != bool(other->parent) ||
+            (parent && other->parent && !parent.value().equals(&other->parent.value()))) {
+        return false;
+    }
+    return std::equal(entries.begin(), entries.end(), other->entries.begin(),
+                      [](const Entry& a, const Entry& b) -> bool {
+        return a.key.equals(&b.key) && a.value->equals(b.value.get());
+    });
+}
+
 Style* Style::clone(StringPool* newPool) const {
     Style* style = new Style();
     style->parent = parent;
@@ -484,6 +571,18 @@
     return out;
 }
 
+bool Array::equals(const Value* value) const {
+    const Array* other = valueCast<Array>(value);
+    if (!other) {
+        return false;
+    }
+
+    return std::equal(items.begin(), items.end(), other->items.begin(),
+                      [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
+        return a->equals(b.get());
+    });
+}
+
 Array* Array::clone(StringPool* newPool) const {
     Array* array = new Array();
     array->mComment = mComment;
@@ -500,6 +599,21 @@
         << "]";
 }
 
+bool Plural::equals(const Value* value) const {
+    const Plural* other = valueCast<Plural>(value);
+    if (!other) {
+        return false;
+    }
+
+    return std::equal(values.begin(), values.end(), other->values.begin(),
+                      [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
+        if (bool(a) != bool(b)) {
+            return false;
+        }
+        return bool(a) == bool(b) || a->equals(b.get());
+    });
+}
+
 Plural* Plural::clone(StringPool* newPool) const {
     Plural* p = new Plural();
     p->mComment = mComment;
@@ -515,12 +629,42 @@
 
 void Plural::print(std::ostream* out) const {
     *out << "(plural)";
+    if (values[Zero]) {
+        *out << " zero=" << *values[Zero];
+    }
+
+    if (values[One]) {
+        *out << " one=" << *values[One];
+    }
+
+    if (values[Two]) {
+        *out << " two=" << *values[Two];
+    }
+
+    if (values[Few]) {
+        *out << " few=" << *values[Few];
+    }
+
+    if (values[Many]) {
+        *out << " many=" << *values[Many];
+    }
 }
 
 static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
     return out << *item;
 }
 
+bool Styleable::equals(const Value* value) const {
+    const Styleable* other = valueCast<Styleable>(value);
+    if (!other) {
+        return false;
+    }
+    return std::equal(entries.begin(), entries.end(), other->entries.begin(),
+                      [](const Reference& a, const Reference& b) -> bool {
+        return a.equals(&b);
+    });
+}
+
 Styleable* Styleable::clone(StringPool* /*newPool*/) const {
     return new Styleable(*this);
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 43354ac..aa1b550 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -54,6 +54,18 @@
         mWeak = val;
     }
 
+    // Whether the value is marked as translateable.
+    // This does not persist when flattened.
+    // It is only used during compilation phase.
+    void setTranslateable(bool val) {
+        mTranslateable = val;
+    }
+
+    // Default true.
+    bool isTranslateable() const {
+        return mTranslateable;
+    }
+
     /**
      * Returns the source where this value was defined.
      */
@@ -84,6 +96,8 @@
         mComment = std::move(str);
     }
 
+    virtual bool equals(const Value* value) const = 0;
+
     /**
      * Calls the appropriate overload of ValueVisitor.
      */
@@ -103,6 +117,7 @@
     Source mSource;
     std::u16string mComment;
     bool mWeak = false;
+    bool mTranslateable = true;
 };
 
 /**
@@ -158,6 +173,7 @@
     explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
     explicit Reference(const ResourceId& i, Type type = Type::kResource);
 
+    bool equals(const Value* value) const override;
     bool flatten(android::Res_value* outValue) const override;
     Reference* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
@@ -168,6 +184,7 @@
  */
 struct Id : public BaseItem<Id> {
     Id() { mWeak = true; }
+    bool equals(const Value* value) const override;
     bool flatten(android::Res_value* out) const override;
     Id* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
@@ -183,6 +200,7 @@
 
     RawString(const StringPool::Ref& ref);
 
+    bool equals(const Value* value) const override;
     bool flatten(android::Res_value* outValue) const override;
     RawString* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
@@ -193,17 +211,10 @@
 
     String(const StringPool::Ref& ref);
 
-    // Whether the string is marked as translateable. This does not persist when flattened.
-    // It is only used during compilation phase.
-    void setTranslateable(bool val);
-    bool isTranslateable() const;
-
+    bool equals(const Value* value) const override;
     bool flatten(android::Res_value* outValue) const override;
     String* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
-
-private:
-    bool mTranslateable;
 };
 
 struct StyledString : public BaseItem<StyledString> {
@@ -211,17 +222,10 @@
 
     StyledString(const StringPool::StyleRef& ref);
 
-    // Whether the string is marked as translateable. This does not persist when flattened.
-    // It is only used during compilation phase.
-    void setTranslateable(bool val);
-    bool isTranslateable() const;
-
+    bool equals(const Value* value) const override;
     bool flatten(android::Res_value* outValue) const override;
     StyledString* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
-
-private:
-    bool mTranslateable;
 };
 
 struct FileReference : public BaseItem<FileReference> {
@@ -235,6 +239,7 @@
     FileReference() = default;
     FileReference(const StringPool::Ref& path);
 
+    bool equals(const Value* value) const override;
     bool flatten(android::Res_value* outValue) const override;
     FileReference* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
@@ -250,6 +255,7 @@
     BinaryPrimitive(const android::Res_value& val);
     BinaryPrimitive(uint8_t dataType, uint32_t data);
 
+    bool equals(const Value* value) const override;
     bool flatten(android::Res_value* outValue) const override;
     BinaryPrimitive* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
@@ -268,6 +274,7 @@
 
     Attribute(bool w, uint32_t t = 0u);
 
+    bool equals(const Value* value) const override;
     Attribute* clone(StringPool* newPool) const override;
     void printMask(std::ostream* out) const;
     void print(std::ostream* out) const override;
@@ -290,6 +297,7 @@
 
     std::vector<Entry> entries;
 
+    bool equals(const Value* value) const override;
     Style* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
 };
@@ -297,6 +305,7 @@
 struct Array : public BaseValue<Array> {
     std::vector<std::unique_ptr<Item>> items;
 
+    bool equals(const Value* value) const override;
     Array* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
 };
@@ -314,6 +323,7 @@
 
     std::array<std::unique_ptr<Item>, Count> values;
 
+    bool equals(const Value* value) const override;
     Plural* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
 };
@@ -321,6 +331,7 @@
 struct Styleable : public BaseValue<Styleable> {
     std::vector<Reference> entries;
 
+    bool equals(const Value* value) const override;
     Styleable* clone(StringPool* newPool) const override;
     void print(std::ostream* out) const override;
 };
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index ea2aa55..b8bc5db 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -127,6 +127,11 @@
     }
 };
 
+template <typename T>
+const T* valueCast(const Value* value) {
+    return valueCast<T>(const_cast<Value*>(value));
+}
+
 /**
  * Returns a valid pointer to T if the Value is of subtype T.
  * Otherwise, returns nullptr.
@@ -141,7 +146,6 @@
     return visitor.value;
 }
 
-
 inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
     for (auto& type : pkg->types) {
         for (auto& entry : type->entries) {
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 99c2077..d080e16 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -128,23 +128,6 @@
             mPool(pool), mMethod(method), mLocalizer(method) {
     }
 
-    void visit(Array* array) override {
-        std::unique_ptr<Array> localized = util::make_unique<Array>();
-        localized->items.resize(array->items.size());
-        for (size_t i = 0; i < array->items.size(); i++) {
-            Visitor subVisitor(mPool, mMethod);
-            array->items[i]->accept(&subVisitor);
-            if (subVisitor.mItem) {
-                localized->items[i] = std::move(subVisitor.mItem);
-            } else {
-                localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool));
-            }
-        }
-        localized->setSource(array->getSource());
-        localized->setWeak(true);
-        mValue = std::move(localized);
-    }
-
     void visit(Plural* plural) override {
         std::unique_ptr<Plural> localized = util::make_unique<Plural>();
         for (size_t i = 0; i < plural->values.size(); i++) {
@@ -164,10 +147,6 @@
     }
 
     void visit(String* string) override {
-        if (!string->isTranslateable()) {
-            return;
-        }
-
         std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) +
                 mLocalizer.end();
         std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
@@ -177,10 +156,6 @@
     }
 
     void visit(StyledString* string) override {
-        if (!string->isTranslateable()) {
-            return;
-        }
-
         mItem = pseudolocalizeStyledString(string, mMethod, mPool);
         mItem->setWeak(true);
     }
@@ -238,14 +213,26 @@
     }
 }
 
+/**
+ * A value is pseudolocalizable if it does not define a locale (or is the default locale)
+ * and is translateable.
+ */
+static bool isPseudolocalizable(ResourceConfigValue* configValue) {
+    const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
+    if (diff & ConfigDescription::CONFIG_LOCALE) {
+        return false;
+    }
+    return configValue->value->isTranslateable();
+}
+
 } // namespace
 
 bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
     for (auto& package : table->packages) {
         for (auto& type : package->types) {
             for (auto& entry : type->entries) {
-                std::vector<ResourceConfigValue*> values = entry->findAllValues(
-                        ConfigDescription::defaultConfig());
+                std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable);
+
                 for (ResourceConfigValue* value : values) {
                     pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
                                            &table->stringPool, entry.get());
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
new file mode 100644
index 0000000..20b7b59
--- /dev/null
+++ b/tools/aapt2/diff/Diff.cpp
@@ -0,0 +1,411 @@
+/*
+ * 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.
+ */
+
+#include "Flags.h"
+#include "ResourceTable.h"
+#include "io/ZipArchive.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "unflatten/BinaryResourceParser.h"
+
+#include <android-base/macros.h>
+
+namespace aapt {
+
+class DiffContext : public IAaptContext {
+public:
+    const std::u16string& getCompilationPackage() override {
+        return mEmpty;
+    }
+
+    uint8_t getPackageId() override {
+        return 0x0;
+    }
+
+    IDiagnostics* getDiagnostics() override {
+        return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+        return &mNameMangler;
+    }
+
+    SymbolTable* getExternalSymbols() override {
+        return &mSymbolTable;
+    }
+
+    bool verbose() override {
+        return false;
+    }
+
+private:
+    std::u16string mEmpty;
+    StdErrDiagnostics mDiagnostics;
+    NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
+    SymbolTable mSymbolTable;
+};
+
+class LoadedApk {
+public:
+    LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+              std::unique_ptr<ResourceTable> table) :
+            mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
+    }
+
+    io::IFileCollection* getFileCollection() {
+        return mApk.get();
+    }
+
+    ResourceTable* getResourceTable() {
+        return mTable.get();
+    }
+
+    const Source& getSource() {
+        return mSource;
+    }
+
+private:
+    Source mSource;
+    std::unique_ptr<io::IFileCollection> mApk;
+    std::unique_ptr<ResourceTable> mTable;
+
+    DISALLOW_COPY_AND_ASSIGN(LoadedApk);
+};
+
+static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
+    Source source(path);
+    std::string error;
+    std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
+    if (!apk) {
+        context->getDiagnostics()->error(DiagMessage(source) << error);
+        return {};
+    }
+
+    io::IFile* file = apk->findFile("resources.arsc");
+    if (!file) {
+        context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
+        return {};
+    }
+
+    std::unique_ptr<io::IData> data = file->openAsData();
+    if (!data) {
+        context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
+        return {};
+    }
+
+    std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+    BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
+    if (!parser.parse()) {
+        return {};
+    }
+
+    return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+}
+
+static void emitDiffLine(const Source& source, const StringPiece& message) {
+    std::cerr << source << ": " << message << "\n";
+}
+
+static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
+    return symbolA.state != symbolB.state;
+}
+
+template <typename Id>
+static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
+                     const Symbol& symbolB, const Maybe<Id>& idB) {
+    if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
+        return idA != idB;
+    }
+    return false;
+}
+
+static bool emitResourceConfigValueDiff(IAaptContext* context,
+                                        LoadedApk* apkA,
+                                        ResourceTablePackage* pkgA,
+                                        ResourceTableType* typeA,
+                                        ResourceEntry* entryA,
+                                        ResourceConfigValue* configValueA,
+                                        LoadedApk* apkB,
+                                        ResourceTablePackage* pkgB,
+                                        ResourceTableType* typeB,
+                                        ResourceEntry* entryB,
+                                        ResourceConfigValue* configValueB) {
+    Value* valueA = configValueA->value.get();
+    Value* valueB = configValueB->value.get();
+    if (!valueA->equals(valueB)) {
+        std::stringstream strStream;
+        strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
+                << " config=" << configValueA->config << " does not match:\n";
+        valueA->print(&strStream);
+        strStream << "\n vs \n";
+        valueB->print(&strStream);
+        emitDiffLine(apkB->getSource(), strStream.str());
+        return true;
+    }
+    return false;
+}
+
+static bool emitResourceEntryDiff(IAaptContext* context,
+                                  LoadedApk* apkA,
+                                  ResourceTablePackage* pkgA,
+                                  ResourceTableType* typeA,
+                                  ResourceEntry* entryA,
+                                  LoadedApk* apkB,
+                                  ResourceTablePackage* pkgB,
+                                  ResourceTableType* typeB,
+                                  ResourceEntry* entryB) {
+    bool diff = false;
+    for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
+        ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
+        if (!configValueB) {
+            std::stringstream strStream;
+            strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
+                    << " config=" << configValueA->config;
+            emitDiffLine(apkB->getSource(), strStream.str());
+            diff = true;
+        } else {
+            diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
+                                                configValueA.get(), apkB, pkgB, typeB, entryB,
+                                                configValueB);
+        }
+    }
+
+    // Check for any newly added config values.
+    for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
+        ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
+        if (!configValueA) {
+            std::stringstream strStream;
+            strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
+                    << " config=" << configValueB->config;
+            emitDiffLine(apkB->getSource(), strStream.str());
+            diff = true;
+        }
+    }
+    return false;
+}
+
+static bool emitResourceTypeDiff(IAaptContext* context,
+                                 LoadedApk* apkA,
+                                 ResourceTablePackage* pkgA,
+                                 ResourceTableType* typeA,
+                                 LoadedApk* apkB,
+                                 ResourceTablePackage* pkgB,
+                                 ResourceTableType* typeB) {
+    bool diff = false;
+    for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
+        ResourceEntry* entryB = typeB->findEntry(entryA->name);
+        if (!entryB) {
+            std::stringstream strStream;
+            strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
+            emitDiffLine(apkB->getSource(), strStream.str());
+            diff = true;
+        } else {
+            if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
+                std::stringstream strStream;
+                strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
+                        << " has different visibility (";
+                if (entryB->symbolStatus.state == SymbolState::kPublic) {
+                    strStream << "PUBLIC";
+                } else {
+                    strStream << "PRIVATE";
+                }
+                strStream << " vs ";
+                if (entryA->symbolStatus.state == SymbolState::kPublic) {
+                    strStream << "PUBLIC";
+                } else {
+                    strStream << "PRIVATE";
+                }
+                strStream << ")";
+                emitDiffLine(apkB->getSource(), strStream.str());
+                diff = true;
+            } else if (isIdDiff(entryA->symbolStatus, entryA->id,
+                                entryB->symbolStatus, entryB->id)) {
+                std::stringstream strStream;
+                strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
+                        << " has different public ID (";
+                if (entryB->id) {
+                    strStream << "0x" << std::hex << entryB->id.value();
+                } else {
+                    strStream << "none";
+                }
+                strStream << " vs ";
+                if (entryA->id) {
+                    strStream << "0x " << std::hex << entryA->id.value();
+                } else {
+                    strStream << "none";
+                }
+                strStream << ")";
+                emitDiffLine(apkB->getSource(), strStream.str());
+                diff = true;
+            }
+            diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
+                                          apkB, pkgB, typeB, entryB);
+        }
+    }
+
+    // Check for any newly added entries.
+    for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
+        ResourceEntry* entryA = typeA->findEntry(entryB->name);
+        if (!entryA) {
+            std::stringstream strStream;
+            strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
+            emitDiffLine(apkB->getSource(), strStream.str());
+            diff = true;
+        }
+    }
+    return diff;
+}
+
+static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
+                                    ResourceTablePackage* pkgA,
+                                    LoadedApk* apkB, ResourceTablePackage* pkgB) {
+    bool diff = false;
+    for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
+        ResourceTableType* typeB = pkgB->findType(typeA->type);
+        if (!typeB) {
+            std::stringstream strStream;
+            strStream << "missing " << pkgA->name << ":" << typeA->type;
+            emitDiffLine(apkA->getSource(), strStream.str());
+            diff = true;
+        } else {
+            if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
+                std::stringstream strStream;
+                strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
+                if (typeB->symbolStatus.state == SymbolState::kPublic) {
+                    strStream << "PUBLIC";
+                } else {
+                    strStream << "PRIVATE";
+                }
+                strStream << " vs ";
+                if (typeA->symbolStatus.state == SymbolState::kPublic) {
+                    strStream << "PUBLIC";
+                } else {
+                    strStream << "PRIVATE";
+                }
+                strStream << ")";
+                emitDiffLine(apkB->getSource(), strStream.str());
+                diff = true;
+            } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
+                std::stringstream strStream;
+                strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
+                if (typeB->id) {
+                    strStream << "0x" << std::hex << typeB->id.value();
+                } else {
+                    strStream << "none";
+                }
+                strStream << " vs ";
+                if (typeA->id) {
+                    strStream << "0x " << std::hex << typeA->id.value();
+                } else {
+                    strStream << "none";
+                }
+                strStream << ")";
+                emitDiffLine(apkB->getSource(), strStream.str());
+                diff = true;
+            }
+            diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
+        }
+    }
+
+    // Check for any newly added types.
+    for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
+        ResourceTableType* typeA = pkgA->findType(typeB->type);
+        if (!typeA) {
+            std::stringstream strStream;
+            strStream << "new type " << pkgB->name << ":" << typeB->type;
+            emitDiffLine(apkB->getSource(), strStream.str());
+            diff = true;
+        }
+    }
+    return diff;
+}
+
+static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
+    ResourceTable* tableA = apkA->getResourceTable();
+    ResourceTable* tableB = apkB->getResourceTable();
+
+    bool diff = false;
+    for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
+        ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
+        if (!pkgB) {
+            std::stringstream strStream;
+            strStream << "missing package " << pkgA->name;
+            emitDiffLine(apkB->getSource(), strStream.str());
+            diff = true;
+        } else {
+            if (pkgA->id != pkgB->id) {
+                std::stringstream strStream;
+                strStream << "package '" << pkgA->name << "' has different id (";
+                if (pkgB->id) {
+                    strStream << "0x" << std::hex << pkgB->id.value();
+                } else {
+                    strStream << "none";
+                }
+                strStream << " vs ";
+                if (pkgA->id) {
+                    strStream << "0x" << std::hex << pkgA->id.value();
+                } else {
+                    strStream << "none";
+                }
+                strStream << ")";
+                emitDiffLine(apkB->getSource(), strStream.str());
+                diff = true;
+            }
+            diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
+        }
+    }
+
+    // Check for any newly added packages.
+    for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
+        ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
+        if (!pkgA) {
+            std::stringstream strStream;
+            strStream << "new package " << pkgB->name;
+            emitDiffLine(apkB->getSource(), strStream.str());
+            diff = true;
+        }
+    }
+    return diff;
+}
+
+int diff(const std::vector<StringPiece>& args) {
+    DiffContext context;
+
+    Flags flags;
+    if (!flags.parse("aapt2 diff", args, &std::cerr)) {
+        return 1;
+    }
+
+    if (flags.getArgs().size() != 2u) {
+        std::cerr << "must have two apks as arguments.\n\n";
+        flags.usage("aapt2 diff", &std::cerr);
+        return 1;
+    }
+
+    std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
+    std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
+    if (!apkA || !apkB) {
+        return 1;
+    }
+
+    if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
+        // We emitted a diff, so return 1 (failure).
+        return 1;
+    }
+    return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 953e87e..77a949f 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -180,7 +180,6 @@
 
     manifestAction[u"uses-configuration"];
     manifestAction[u"uses-feature"];
-    manifestAction[u"uses-library"];
     manifestAction[u"supports-screens"];
     manifestAction[u"compatible-screens"];
     manifestAction[u"supports-gl-texture"];
@@ -189,6 +188,9 @@
     xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"];
     applicationAction.action(optionalNameIsJavaClassName);
 
+    // Uses library actions.
+    applicationAction[u"uses-library"];
+
     // Activity actions.
     applicationAction[u"activity"].action(requiredNameIsJavaClassName);
     applicationAction[u"activity"][u"intent-filter"] = intentFilterAction;
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 82e4fb0..1ec48f0 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -234,6 +234,8 @@
                 const pb::Attribute& pbAttr = pbCompoundValue.attr();
                 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
                 attr->typeMask = pbAttr.format_flags();
+                attr->minInt = pbAttr.min_int();
+                attr->maxInt = pbAttr.max_int();
                 for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
                     Attribute::Symbol symbol;
                     deserializeItemCommon(pbSymbol, &symbol.symbol);