Merge "Camera2: Handle binder death on ICameraDeviceUser" into nyc-dev
diff --git a/Android.mk b/Android.mk
index ffc0421..08f3468 100644
--- a/Android.mk
+++ b/Android.mk
@@ -149,6 +149,7 @@
 	core/java/android/content/pm/IPackageMoveObserver.aidl \
 	core/java/android/content/pm/IPackageStatsObserver.aidl \
 	core/java/android/content/pm/IOnPermissionsChangeListener.aidl \
+	core/java/android/content/pm/IShortcutService.aidl \
 	core/java/android/database/IContentObserver.aidl \
 	../av/camera/aidl/android/hardware/ICameraService.aidl \
 	../av/camera/aidl/android/hardware/ICameraServiceListener.aidl \
@@ -652,6 +653,7 @@
 	frameworks/base/core/java/android/content/pm/ProviderInfo.aidl \
 	frameworks/base/core/java/android/content/pm/PackageStats.aidl \
 	frameworks/base/core/java/android/content/pm/PermissionGroupInfo.aidl \
+	frameworks/base/core/java/android/content/pm/ShortcutInfo.aidl \
 	frameworks/base/core/java/android/content/pm/LabeledIntent.aidl \
 	frameworks/base/core/java/android/content/ComponentName.aidl \
 	frameworks/base/core/java/android/content/SyncStats.aidl \
diff --git a/api/current.txt b/api/current.txt
index 5ec93cb..cc386db 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5885,7 +5885,7 @@
     method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName);
     method public android.os.Bundle getUserRestrictions(android.content.ComponentName);
-    method public java.lang.String getWifiMacAddress();
+    method public java.lang.String getWifiMacAddress(android.content.ComponentName);
     method public boolean hasCaCertInstalled(android.content.ComponentName, byte[]);
     method public boolean hasGrantedPolicy(android.content.ComponentName, int);
     method public boolean installCaCert(android.content.ComponentName, byte[]);
@@ -8140,6 +8140,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 TELECOM_SERVICE = "telecom";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
@@ -9445,13 +9446,19 @@
   public class LauncherApps {
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
     method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle);
+    method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<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 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 void unregisterCallback(android.content.pm.LauncherApps.Callback);
   }
 
@@ -9464,6 +9471,18 @@
     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 void setActivity(android.content.ComponentName);
+    method public void setChangedSince(long);
+    method public void setPackage(java.lang.String);
+    method public void setQueryFlags(int);
+    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_PINNED = 2; // 0x2
   }
 
   public class PackageInfo implements android.os.Parcelable {
@@ -9963,6 +9982,56 @@
     field public java.lang.String permission;
   }
 
+  public class ShortcutInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.content.ComponentName getActivityComponent();
+    method public android.os.PersistableBundle getExtras();
+    method public java.lang.String getId();
+    method public android.content.Intent getIntent();
+    method public long getLastChangedTimestamp();
+    method public java.lang.String getPackageName();
+    method public java.lang.String getTitle();
+    method public int getWeight();
+    method public boolean hasIconFile();
+    method public boolean hasIconResource();
+    method public boolean isDynamic();
+    method public boolean isPinned();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
+    field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3
+    field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4
+    field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final int FLAG_DYNAMIC = 1; // 0x1
+    field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
+    field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+    field public static final int FLAG_PINNED = 2; // 0x2
+  }
+
+  public static class ShortcutInfo.Builder {
+    ctor public ShortcutInfo.Builder(android.content.Context);
+    method public android.content.pm.ShortcutInfo build();
+    method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+    method public android.content.pm.ShortcutInfo.Builder 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 setTitle(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setWeight(int);
+  }
+
+  public class ShortcutManager {
+    method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo);
+    method public void deleteAllDynamicShortcuts();
+    method public void deleteDynamicShortcut(java.lang.String);
+    method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
+    method public int getMaxDynamicShortcutCount();
+    method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
+    method public long getRateLimitResetTime();
+    method public int getRemainingCallCount();
+    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);
@@ -23023,6 +23092,7 @@
     method public boolean onTouchEvent(android.view.MotionEvent);
     method public boolean onTrackballEvent(android.view.MotionEvent);
     method public abstract boolean onTune(android.net.Uri);
+    method public boolean onTune(android.net.Uri, android.os.Bundle);
     method public void onUnblockContent(android.media.tv.TvContentRating);
     method public void setOverlayViewEnabled(boolean);
   }
@@ -23096,12 +23166,15 @@
     method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
     method public void setStreamVolume(float);
     method public void setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback);
+    method public void setZOrderMediaOverlay(boolean);
+    method public void setZOrderOnTop(boolean);
     method public void timeShiftPause();
     method public void timeShiftPlay(java.lang.String, android.net.Uri);
     method public void timeShiftResume();
     method public void timeShiftSeekTo(long);
     method public void timeShiftSetPlaybackParams(android.media.PlaybackParams);
     method public void tune(java.lang.String, android.net.Uri);
+    method public void tune(java.lang.String, android.net.Uri, android.os.Bundle);
   }
 
   public static abstract interface TvView.OnUnhandledInputEventListener {
@@ -29007,6 +29080,7 @@
     ctor public PersistableBundle();
     ctor public PersistableBundle(int);
     ctor public PersistableBundle(android.os.PersistableBundle);
+    ctor public PersistableBundle(android.os.Bundle);
     method public java.lang.Object clone();
     method public int describeContents();
     method public android.os.PersistableBundle getPersistableBundle(java.lang.String);
@@ -34426,8 +34500,9 @@
 package android.service.notification {
 
   public class Condition implements android.os.Parcelable {
-    ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int);
+    ctor public Condition(android.net.Uri, java.lang.String, int);
     ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
+    ctor public Condition(android.os.Parcel);
     method public android.service.notification.Condition copy();
     method public int describeContents();
     method public static boolean isValidId(android.net.Uri, java.lang.String);
@@ -34458,6 +34533,7 @@
     method public final void notifyConditions(android.service.notification.Condition...);
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract void onConnected();
+    method public void onRequestConditions(int);
     method public abstract void onSubscribe(android.net.Uri);
     method public abstract void onUnsubscribe(android.net.Uri);
     field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
@@ -57404,7 +57480,23 @@
 
   public abstract interface Comparator {
     method public abstract int compare(T, T);
+    method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>);
+    method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>);
+    method public static java.util.Comparator<T> comparingDouble(java.util.function.ToDoubleFunction<? super T>);
+    method public static java.util.Comparator<T> comparingInt(java.util.function.ToIntFunction<? super T>);
+    method public static java.util.Comparator<T> comparingLong(java.util.function.ToLongFunction<? super T>);
     method public abstract boolean equals(java.lang.Object);
+    method public static java.util.Comparator<T> naturalOrder();
+    method public static java.util.Comparator<T> nullsFirst(java.util.Comparator<? super T>);
+    method public static java.util.Comparator<T> nullsLast(java.util.Comparator<? super T>);
+    method public static java.util.Comparator<T> reverseOrder();
+    method public default java.util.Comparator<T> reversed();
+    method public default java.util.Comparator<T> thenComparing(java.util.Comparator<? super T>);
+    method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>);
+    method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>);
+    method public default java.util.Comparator<T> thenComparingDouble(java.util.function.ToDoubleFunction<? super T>);
+    method public default java.util.Comparator<T> thenComparingInt(java.util.function.ToIntFunction<? super T>);
+    method public default java.util.Comparator<T> thenComparingLong(java.util.function.ToLongFunction<? super T>);
   }
 
   public class ConcurrentModificationException extends java.lang.RuntimeException {
diff --git a/api/removed.txt b/api/removed.txt
index 115224c..2f55373 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -43,6 +43,14 @@
 
 }
 
+package android.media.tv {
+
+  public class TvView extends android.view.ViewGroup {
+    method public void requestUnblockContent(android.media.tv.TvContentRating);
+  }
+
+}
+
 package android.net {
 
   public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
@@ -201,14 +209,6 @@
 
 }
 
-package android.service.notification {
-
-  public abstract class ConditionProviderService extends android.app.Service {
-    method public void onRequestConditions(int);
-  }
-
-}
-
 package android.test.mock {
 
   public deprecated class MockPackageManager extends android.content.pm.PackageManager {
diff --git a/api/system-current.txt b/api/system-current.txt
index 4224792..7823b3c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6031,7 +6031,7 @@
     method public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName);
     method public int getUserProvisioningState();
     method public android.os.Bundle getUserRestrictions(android.content.ComponentName);
-    method public java.lang.String getWifiMacAddress();
+    method public java.lang.String getWifiMacAddress(android.content.ComponentName);
     method public boolean hasCaCertInstalled(android.content.ComponentName, byte[]);
     method public boolean hasGrantedPolicy(android.content.ComponentName, int);
     method public boolean installCaCert(android.content.ComponentName, byte[]);
@@ -8446,6 +8446,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 TELECOM_SERVICE = "telecom";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
@@ -9779,13 +9780,19 @@
   public class LauncherApps {
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
     method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle);
+    method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<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 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 void unregisterCallback(android.content.pm.LauncherApps.Callback);
   }
 
@@ -9798,6 +9805,18 @@
     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 void setActivity(android.content.ComponentName);
+    method public void setChangedSince(long);
+    method public void setPackage(java.lang.String);
+    method public void setQueryFlags(int);
+    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_PINNED = 2; // 0x2
   }
 
   public class PackageInfo implements android.os.Parcelable {
@@ -10357,6 +10376,56 @@
     field public java.lang.String permission;
   }
 
+  public class ShortcutInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.content.ComponentName getActivityComponent();
+    method public android.os.PersistableBundle getExtras();
+    method public java.lang.String getId();
+    method public android.content.Intent getIntent();
+    method public long getLastChangedTimestamp();
+    method public java.lang.String getPackageName();
+    method public java.lang.String getTitle();
+    method public int getWeight();
+    method public boolean hasIconFile();
+    method public boolean hasIconResource();
+    method public boolean isDynamic();
+    method public boolean isPinned();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
+    field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3
+    field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4
+    field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final int FLAG_DYNAMIC = 1; // 0x1
+    field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
+    field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+    field public static final int FLAG_PINNED = 2; // 0x2
+  }
+
+  public static class ShortcutInfo.Builder {
+    ctor public ShortcutInfo.Builder(android.content.Context);
+    method public android.content.pm.ShortcutInfo build();
+    method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+    method public android.content.pm.ShortcutInfo.Builder 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 setTitle(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setWeight(int);
+  }
+
+  public class ShortcutManager {
+    method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo);
+    method public void deleteAllDynamicShortcuts();
+    method public void deleteDynamicShortcut(java.lang.String);
+    method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
+    method public int getMaxDynamicShortcutCount();
+    method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
+    method public long getRateLimitResetTime();
+    method public int getRemainingCallCount();
+    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);
@@ -24872,7 +24941,6 @@
     method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
     method protected void onLayout(boolean, int, int, int, int);
     method public boolean onUnhandledInputEvent(android.view.InputEvent);
-    method public deprecated void requestUnblockContent(android.media.tv.TvContentRating);
     method public void reset();
     method public void selectTrack(int, java.lang.String);
     method public void sendAppPrivateCommand(java.lang.String, android.os.Bundle);
@@ -31292,6 +31360,7 @@
     ctor public PersistableBundle();
     ctor public PersistableBundle(int);
     ctor public PersistableBundle(android.os.PersistableBundle);
+    ctor public PersistableBundle(android.os.Bundle);
     method public java.lang.Object clone();
     method public int describeContents();
     method public android.os.PersistableBundle getPersistableBundle(java.lang.String);
@@ -36916,8 +36985,9 @@
 package android.service.notification {
 
   public class Condition implements android.os.Parcelable {
-    ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int);
+    ctor public Condition(android.net.Uri, java.lang.String, int);
     ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
+    ctor public Condition(android.os.Parcel);
     method public android.service.notification.Condition copy();
     method public int describeContents();
     method public static boolean isValidId(android.net.Uri, java.lang.String);
@@ -60508,7 +60578,23 @@
 
   public abstract interface Comparator {
     method public abstract int compare(T, T);
+    method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>);
+    method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>);
+    method public static java.util.Comparator<T> comparingDouble(java.util.function.ToDoubleFunction<? super T>);
+    method public static java.util.Comparator<T> comparingInt(java.util.function.ToIntFunction<? super T>);
+    method public static java.util.Comparator<T> comparingLong(java.util.function.ToLongFunction<? super T>);
     method public abstract boolean equals(java.lang.Object);
+    method public static java.util.Comparator<T> naturalOrder();
+    method public static java.util.Comparator<T> nullsFirst(java.util.Comparator<? super T>);
+    method public static java.util.Comparator<T> nullsLast(java.util.Comparator<? super T>);
+    method public static java.util.Comparator<T> reverseOrder();
+    method public default java.util.Comparator<T> reversed();
+    method public default java.util.Comparator<T> thenComparing(java.util.Comparator<? super T>);
+    method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>);
+    method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>);
+    method public default java.util.Comparator<T> thenComparingDouble(java.util.function.ToDoubleFunction<? super T>);
+    method public default java.util.Comparator<T> thenComparingInt(java.util.function.ToIntFunction<? super T>);
+    method public default java.util.Comparator<T> thenComparingLong(java.util.function.ToLongFunction<? super T>);
   }
 
   public class ConcurrentModificationException extends java.lang.RuntimeException {
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 9ebc484..79f7297 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -41,6 +41,14 @@
 
 }
 
+package android.media.tv {
+
+  public class TvView extends android.view.ViewGroup {
+    method public void requestUnblockContent(android.media.tv.TvContentRating);
+  }
+
+}
+
 package android.net {
 
   public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
diff --git a/api/test-current.txt b/api/test-current.txt
index 894971b..a478793 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5889,7 +5889,7 @@
     method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName);
     method public android.os.Bundle getUserRestrictions(android.content.ComponentName);
-    method public java.lang.String getWifiMacAddress();
+    method public java.lang.String getWifiMacAddress(android.content.ComponentName);
     method public boolean hasCaCertInstalled(android.content.ComponentName, byte[]);
     method public boolean hasGrantedPolicy(android.content.ComponentName, int);
     method public boolean installCaCert(android.content.ComponentName, byte[]);
@@ -8146,6 +8146,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 TELECOM_SERVICE = "telecom";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
@@ -9454,13 +9455,19 @@
   public class LauncherApps {
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
     method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
+    method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle);
+    method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<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 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 void unregisterCallback(android.content.pm.LauncherApps.Callback);
   }
 
@@ -9473,6 +9480,18 @@
     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 void setActivity(android.content.ComponentName);
+    method public void setChangedSince(long);
+    method public void setPackage(java.lang.String);
+    method public void setQueryFlags(int);
+    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_PINNED = 2; // 0x2
   }
 
   public class PackageInfo implements android.os.Parcelable {
@@ -9973,6 +9992,56 @@
     field public java.lang.String permission;
   }
 
+  public class ShortcutInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.content.ComponentName getActivityComponent();
+    method public android.os.PersistableBundle getExtras();
+    method public java.lang.String getId();
+    method public android.content.Intent getIntent();
+    method public long getLastChangedTimestamp();
+    method public java.lang.String getPackageName();
+    method public java.lang.String getTitle();
+    method public int getWeight();
+    method public boolean hasIconFile();
+    method public boolean hasIconResource();
+    method public boolean isDynamic();
+    method public boolean isPinned();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
+    field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3
+    field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4
+    field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final int FLAG_DYNAMIC = 1; // 0x1
+    field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
+    field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+    field public static final int FLAG_PINNED = 2; // 0x2
+  }
+
+  public static class ShortcutInfo.Builder {
+    ctor public ShortcutInfo.Builder(android.content.Context);
+    method public android.content.pm.ShortcutInfo build();
+    method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+    method public android.content.pm.ShortcutInfo.Builder 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 setTitle(java.lang.String);
+    method public android.content.pm.ShortcutInfo.Builder setWeight(int);
+  }
+
+  public class ShortcutManager {
+    method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo);
+    method public void deleteAllDynamicShortcuts();
+    method public void deleteDynamicShortcut(java.lang.String);
+    method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
+    method public int getMaxDynamicShortcutCount();
+    method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
+    method public long getRateLimitResetTime();
+    method public int getRemainingCallCount();
+    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);
@@ -23034,6 +23103,7 @@
     method public boolean onTouchEvent(android.view.MotionEvent);
     method public boolean onTrackballEvent(android.view.MotionEvent);
     method public abstract boolean onTune(android.net.Uri);
+    method public boolean onTune(android.net.Uri, android.os.Bundle);
     method public void onUnblockContent(android.media.tv.TvContentRating);
     method public void setOverlayViewEnabled(boolean);
   }
@@ -23107,12 +23177,15 @@
     method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
     method public void setStreamVolume(float);
     method public void setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback);
+    method public void setZOrderMediaOverlay(boolean);
+    method public void setZOrderOnTop(boolean);
     method public void timeShiftPause();
     method public void timeShiftPlay(java.lang.String, android.net.Uri);
     method public void timeShiftResume();
     method public void timeShiftSeekTo(long);
     method public void timeShiftSetPlaybackParams(android.media.PlaybackParams);
     method public void tune(java.lang.String, android.net.Uri);
+    method public void tune(java.lang.String, android.net.Uri, android.os.Bundle);
   }
 
   public static abstract interface TvView.OnUnhandledInputEventListener {
@@ -29018,6 +29091,7 @@
     ctor public PersistableBundle();
     ctor public PersistableBundle(int);
     ctor public PersistableBundle(android.os.PersistableBundle);
+    ctor public PersistableBundle(android.os.Bundle);
     method public java.lang.Object clone();
     method public int describeContents();
     method public android.os.PersistableBundle getPersistableBundle(java.lang.String);
@@ -34443,8 +34517,9 @@
 package android.service.notification {
 
   public class Condition implements android.os.Parcelable {
-    ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int);
+    ctor public Condition(android.net.Uri, java.lang.String, int);
     ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
+    ctor public Condition(android.os.Parcel);
     method public android.service.notification.Condition copy();
     method public int describeContents();
     method public static boolean isValidId(android.net.Uri, java.lang.String);
@@ -34475,6 +34550,7 @@
     method public final void notifyConditions(android.service.notification.Condition...);
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract void onConnected();
+    method public void onRequestConditions(int);
     method public abstract void onSubscribe(android.net.Uri);
     method public abstract void onUnsubscribe(android.net.Uri);
     field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
@@ -57423,7 +57499,23 @@
 
   public abstract interface Comparator {
     method public abstract int compare(T, T);
+    method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>);
+    method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>);
+    method public static java.util.Comparator<T> comparingDouble(java.util.function.ToDoubleFunction<? super T>);
+    method public static java.util.Comparator<T> comparingInt(java.util.function.ToIntFunction<? super T>);
+    method public static java.util.Comparator<T> comparingLong(java.util.function.ToLongFunction<? super T>);
     method public abstract boolean equals(java.lang.Object);
+    method public static java.util.Comparator<T> naturalOrder();
+    method public static java.util.Comparator<T> nullsFirst(java.util.Comparator<? super T>);
+    method public static java.util.Comparator<T> nullsLast(java.util.Comparator<? super T>);
+    method public static java.util.Comparator<T> reverseOrder();
+    method public default java.util.Comparator<T> reversed();
+    method public default java.util.Comparator<T> thenComparing(java.util.Comparator<? super T>);
+    method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>);
+    method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>);
+    method public default java.util.Comparator<T> thenComparingDouble(java.util.function.ToDoubleFunction<? super T>);
+    method public default java.util.Comparator<T> thenComparingInt(java.util.function.ToIntFunction<? super T>);
+    method public default java.util.Comparator<T> thenComparingLong(java.util.function.ToLongFunction<? super T>);
   }
 
   public class ConcurrentModificationException extends java.lang.RuntimeException {
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 115224c..2f55373 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -43,6 +43,14 @@
 
 }
 
+package android.media.tv {
+
+  public class TvView extends android.view.ViewGroup {
+    method public void requestUnblockContent(android.media.tv.TvContentRating);
+  }
+
+}
+
 package android.net {
 
   public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
@@ -201,14 +209,6 @@
 
 }
 
-package android.service.notification {
-
-  public abstract class ConditionProviderService extends android.app.Service {
-    method public void onRequestConditions(int);
-  }
-
-}
-
 package android.test.mock {
 
   public deprecated class MockPackageManager extends android.content.pm.PackageManager {
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 1f1f318..cd4ace6 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -42,7 +42,7 @@
      * @param name The name of the rule.
      * @param owner The Condition Provider service that owns this rule.
      * @param conditionId A representation of the state that should cause the Condition Provider
-     *                    service to apply the interruption filter.
+     *                    service to apply the given interruption filter.
      * @param interruptionFilter The interruption filter defines which notifications are allowed to
      *                           interrupt the user (e.g. via sound &amp; vibration) while this rule
      *                           is active.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d8f0ac5..a2cc6b1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -47,6 +47,7 @@
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.style.AbsoluteSizeSpan;
+import android.text.style.CharacterStyle;
 import android.text.style.RelativeSizeSpan;
 import android.text.style.TextAppearanceSpan;
 import android.util.Log;
@@ -1664,17 +1665,22 @@
             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
             for (Object span : spans) {
                 Object resultSpan = span;
-                if (span instanceof TextAppearanceSpan) {
-                    TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
+                if (resultSpan instanceof CharacterStyle) {
+                    resultSpan = ((CharacterStyle) span).getUnderlying();
+                }
+                if (resultSpan instanceof TextAppearanceSpan) {
+                    TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
                     resultSpan = new TextAppearanceSpan(
                             originalSpan.getFamily(),
                             originalSpan.getTextStyle(),
                             -1,
                             originalSpan.getTextColor(),
                             originalSpan.getLinkTextColor());
-                } else if (span instanceof RelativeSizeSpan
-                        || span instanceof AbsoluteSizeSpan) {
+                } else if (resultSpan instanceof RelativeSizeSpan
+                        || resultSpan instanceof AbsoluteSizeSpan) {
                     continue;
+                } else {
+                    resultSpan = span;
                 }
                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
                         ss.getSpanFlags(span));
@@ -3591,37 +3597,53 @@
         }
 
         /**
+         * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
+         * change.
+         *
+         * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
+         *
          * @hide
          */
-        public static void stripForDelivery(Notification n) {
+        public static Notification maybeCloneStrippedForDelivery(Notification n) {
             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
-            if (TextUtils.isEmpty(templateClass)) {
-                return;
-            }
+
             // Only strip views for known Styles because we won't know how to
             // re-create them otherwise.
-            if (getNotificationStyleClass(templateClass) == null) {
-                return;
+            if (!TextUtils.isEmpty(templateClass)
+                    && getNotificationStyleClass(templateClass) == null) {
+                return n;
             }
-            // Get rid of unmodified BuilderRemoteViews.
-            if (n.contentView instanceof BuilderRemoteViews &&
+
+            // Only strip unmodified BuilderRemoteViews.
+            boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.contentView.getSequenceNumber()) {
-                n.contentView = null;
-                n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
-            }
-            if (n.bigContentView instanceof BuilderRemoteViews &&
+                            n.contentView.getSequenceNumber();
+            boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.bigContentView.getSequenceNumber()) {
-                n.bigContentView = null;
-                n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
-            }
-            if (n.headsUpContentView instanceof BuilderRemoteViews &&
+                            n.bigContentView.getSequenceNumber();
+            boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.headsUpContentView.getSequenceNumber()) {
-                n.headsUpContentView = null;
-                n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
+                            n.headsUpContentView.getSequenceNumber();
+
+            // Nothing to do here, no need to clone.
+            if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
+                return n;
             }
+
+            Notification clone = n.clone();
+            if (stripContentView) {
+                clone.contentView = null;
+                clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
+            }
+            if (stripBigContentView) {
+                clone.bigContentView = null;
+                clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
+            }
+            if (stripHeadsUpContentView) {
+                clone.headsUpContentView = null;
+                clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
+            }
+            return clone;
         }
 
         private int getBaseLayoutResource() {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 16aee78..7cfa0cd 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -259,8 +259,7 @@
             }
         }
         if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
-        final Notification copy = notification.clone();
-        Builder.stripForDelivery(copy);
+        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
         try {
             service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                     copy, idOut, user.getIdentifier());
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index b1c5fd8..3a5dd30 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -37,7 +37,9 @@
 import android.content.IRestrictionsManager;
 import android.content.RestrictionsManager;
 import android.content.pm.ILauncherApps;
+import android.content.pm.IShortcutService;
 import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutManager;
 import android.content.res.Resources;
 import android.hardware.ConsumerIrManager;
 import android.hardware.ISerialManager;
@@ -748,6 +750,15 @@
                 Log.i(TAG, "Creating new instance of SoundTriggerManager object.");
                 return new SoundTriggerManager(ctx, ISoundTriggerService.Stub.asInterface(b));
             }});
+
+        registerService(Context.SHORTCUT_SERVICE, ShortcutManager.class,
+                new CachedServiceFetcher<ShortcutManager>() {
+                    @Override
+                    public ShortcutManager createService(ContextImpl ctx) {
+                        IBinder b = ServiceManager.getService(Context.SHORTCUT_SERVICE);
+                        return new ShortcutManager(ctx,
+                                IShortcutService.Stub.asInterface(b));
+                    }});
     }
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c4037f8..8bd40dc 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5542,14 +5542,15 @@
     /**
      * Called by device owner to get the MAC address of the Wi-Fi device.
      *
+     * @param admin Which device owner this request is associated with.
      * @return the MAC address of the Wi-Fi device, or null when the information is not
      * available. (For example, Wi-Fi hasn't been enabled, or the device doesn't support Wi-Fi.)
      *
      * <p>The address will be in the {@code XX:XX:XX:XX:XX:XX} format.
      */
-    public String getWifiMacAddress() {
+    public String getWifiMacAddress(@NonNull ComponentName admin) {
         try {
-            return mService.getWifiMacAddress();
+            return mService.getWifiMacAddress(admin);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b7a16aa..dc73e26 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -262,7 +262,7 @@
     List<String> getKeepUninstalledPackages(in ComponentName admin);
     boolean isManagedProfile(in ComponentName admin);
     boolean isSystemOnlyUser(in ComponentName admin);
-    String getWifiMacAddress();
+    String getWifiMacAddress(in ComponentName admin);
     void reboot(in ComponentName admin);
 
     void setShortSupportMessage(in ComponentName admin, in String message);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b935b25..f96ddf0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2680,6 +2680,7 @@
             RADIO_SERVICE,
             HARDWARE_PROPERTIES_SERVICE,
             //@hide: SOUND_TRIGGER_SERVICE,
+            SHORTCUT_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3576,6 +3577,14 @@
     public static final String HARDWARE_PROPERTIES_SERVICE = "hardwareproperties";
 
     /**
+     * TODO Javadoc
+     *
+     * @see #getSystemService
+     * @see android.content.pm.ShortcutManager
+     */
+    public static final String SHORTCUT_SERVICE = "shortcut";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index cc266c5..cf3e298 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -22,6 +22,7 @@
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -42,4 +43,13 @@
     boolean isPackageEnabled(String packageName, in UserHandle user);
     boolean isActivityEnabled(in ComponentName component, in UserHandle user);
     ApplicationInfo getApplicationInfo(String packageName, int flags, in UserHandle user);
+
+    ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
+            in ComponentName componentName, int flags, in UserHandle user);
+    ParceledListSlice getShortcutInfo(String callingPackage, String packageName, in List<String> ids,
+            in UserHandle user);
+    void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
+            in UserHandle user);
+    boolean startShortcut(String callingPackage, String packageName, String id,
+            in Rect sourceBounds, in Bundle startActivityOptions, in UserHandle user);
 }
diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl
index 1303696..e6525af 100644
--- a/core/java/android/content/pm/IOnAppsChangedListener.aidl
+++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.pm.ParceledListSlice;
 import android.os.UserHandle;
 
 /**
@@ -29,4 +30,5 @@
     void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing);
     void onPackagesSuspended(in UserHandle user, in String[] packageNames);
     void onPackagesUnsuspended(in UserHandle user, in String[] packageNames);
+    void onShortcutChanged(in UserHandle user, String packageName, in ParceledListSlice shortcuts);
 }
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
new file mode 100644
index 0000000..23e671d
--- /dev/null
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.content.pm;
+
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
+
+/**
+ * {@hide}
+ */
+interface IShortcutService {
+
+    boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
+            int userId);
+
+    ParceledListSlice getDynamicShortcuts(String packageName, int userId);
+
+    boolean addDynamicShortcut(String packageName, in ShortcutInfo shortcutInfo, int userId);
+
+    void deleteDynamicShortcut(String packageName, in String shortcutId, int userId);
+
+    void deleteAllDynamicShortcuts(String packageName, int userId);
+
+    ParceledListSlice getPinnedShortcuts(String packageName, int userId);
+
+    boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId);
+
+    int getMaxDynamicShortcutCount(String packageName, int userId);
+
+    int getRemainingCallCount(String packageName, int userId);
+
+    long getRateLimitResetTime(String packageName, int userId);
+
+    void resetThrottling(); // system only API for developer opsions
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index e443d50..8e4a0e2 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -16,23 +16,29 @@
 
 package android.content.pm;
 
+import android.Manifest.permission;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ILauncherApps;
-import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -148,6 +154,91 @@
          */
         public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
         }
+
+        /**
+         * Indicates that one or more shortcuts (which may be dynamic and/or pinned)
+         * have been added, updated or removed.
+         *
+         * @param packageName The name of the package that has the shortcuts.
+         * @param shortcuts all shortcuts from the package (dynamic and/or pinned).
+         * @param user The UserHandle of the profile that generated the change.
+         */
+        public void onShortcutsChanged(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+        }
+    }
+
+    /**
+     * Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
+     */
+    public static class ShortcutQuery {
+        /**
+         * Include dynamic shortcuts in the result.
+         */
+        public static final int FLAG_GET_DYNAMIC = 1 << 0;
+
+        /**
+         * Include pinned shortcuts in the result.
+         */
+        public static final int FLAG_GET_PINNED = 1 << 1;
+
+        /**
+         * Requests "key" fields only.
+         */
+        public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
+
+        /** @hide */
+        @IntDef(flag = true,
+                value = {
+                        FLAG_GET_DYNAMIC,
+                        FLAG_GET_PINNED,
+                        FLAG_GET_KEY_FIELDS_ONLY,
+                })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface QueryFlags {}
+
+        long mChangedSince;
+
+        @Nullable
+        String mPackage;
+
+        @Nullable
+        ComponentName mActivity;
+
+        @QueryFlags
+        int mQueryFlags;
+
+        public ShortcutQuery() {
+        }
+
+        /**
+         * 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) {
+            mChangedSince = changedSince;
+        }
+
+        /**
+         * If non-null, returns only shortcuts from the package.
+         */
+        public void setPackage(@Nullable String packageName) {
+            mPackage = packageName;
+        }
+
+        /**
+         * If non-null, returns only shortcuts associated with the activity.
+         */
+        public void setActivity(@Nullable ComponentName activity) {
+            mActivity = activity;
+        }
+
+        /**
+         * Set query options.
+         */
+        public void setQueryFlags(@QueryFlags int queryFlags) {
+            mQueryFlags = queryFlags;
+        }
     }
 
     /** @hide */
@@ -302,6 +393,128 @@
         }
     }
 
+    /**
+     * Returns the IDs of {@link ShortcutInfo}s that match {@code query}.
+     *
+     * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
+     *
+     * @param query result includes shortcuts matching this query.
+     * @param user The UserHandle of the profile.
+     *
+     * @return the IDs of {@link ShortcutInfo}s that match the query.
+     */
+    @RequiresPermission(permission.BIND_APPWIDGET)
+    @Nullable
+    public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
+            @NonNull UserHandle user) {
+        try {
+            return mService.getShortcuts(mContext.getPackageName(),
+                    query.mChangedSince, query.mPackage, query.mActivity, query.mQueryFlags, user)
+                    .getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@link ShortcutInfo}s with the given IDs from a package.
+     *
+     * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
+     *
+     * @param packageName The target package.
+     * @param ids IDs of the shortcuts to retrieve.
+     * @param user The UserHandle of the profile.
+     *
+     * @return list of {@link ShortcutInfo} associated with the package.
+     */
+    @RequiresPermission(permission.BIND_APPWIDGET)
+    @Nullable
+    public List<ShortcutInfo> getShortcutInfo(@NonNull String packageName,
+            @NonNull List<String> ids, @NonNull UserHandle user) {
+        try {
+            return mService.getShortcutInfo(mContext.getPackageName(), packageName, ids, user)
+                    .getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Pin shortcuts on a package.
+     *
+     * <p>This API is <b>NOT</b> cumulative; this will replace all pinned shortcuts for the package.
+     * However, different launchers may have different set of pinned shortcuts.
+     *
+     * <p>Callers must have the {@link permission#BIND_APPWIDGET} permission.
+     *
+     * @param packageName The target package name.
+     * @param shortcutIds The IDs of the shortcut to be pinned.
+     * @param user The UserHandle of the profile.
+     */
+    @RequiresPermission(permission.BIND_APPWIDGET)
+    public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+            @NonNull UserHandle user) {
+        try {
+            mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the icon resource ID, if {@code shortcut} has one
+     * (i.e. when {@link ShortcutInfo#hasIconResource()} returns {@code true}).
+     *
+     * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
+     *
+     * @param shortcut The target shortcut.
+     * @param user The UserHandle of the profile.
+     */
+    @RequiresPermission(permission.BIND_APPWIDGET)
+    public int getShortcutIconResId(@NonNull ShortcutInfo shortcut, @NonNull UserHandle user) {
+        throw new RuntimeException("not implemented yet");
+    }
+
+    /**
+     * Return the icon as {@link ParcelFileDescriptor}, when it's stored as a file
+     * (i.e. when {@link ShortcutInfo#hasIconFile()} returns {@code true}).
+     *
+     * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
+     *
+     * @param shortcut The target shortcut.
+     * @param user The UserHandle of the profile.
+     */
+    @RequiresPermission(permission.BIND_APPWIDGET)
+    public ParcelFileDescriptor getShortcutIconFd(
+            @NonNull ShortcutInfo shortcut, @NonNull UserHandle user) {
+        throw new RuntimeException("not implemented yet");
+    }
+
+    /**
+     * Launches a shortcut.
+     *
+     * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
+     *
+     * @param packageName The target shortcut package name.
+     * @param shortcutId The target shortcut ID.
+     * @param sourceBounds The Rect containing the source bounds of the clicked icon.
+     * @param startActivityOptions Options to pass to startActivity.
+     * @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.
+     */
+    @RequiresPermission(permission.BIND_APPWIDGET)
+    public boolean startShortcut(@NonNull String packageName, @NonNull String shortcutId,
+            @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
+            @NonNull UserHandle user) {
+        try {
+            return mService.startShortcut(mContext.getPackageName(), packageName, shortcutId,
+                    sourceBounds, startActivityOptions, user);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Registers a callback for changes to packages in current and managed profiles.
@@ -474,6 +687,20 @@
                 }
             }
         }
+
+        @Override
+        public void onShortcutChanged(UserHandle user, String packageName,
+                ParceledListSlice shortcuts) {
+            if (DEBUG) {
+                Log.d(TAG, "onShortcutChanged " + user.getIdentifier() + "," + packageName);
+            }
+            final List<ShortcutInfo> list = shortcuts.getList();
+            synchronized (LauncherApps.this) {
+                for (CallbackMessageHandler callback : mCallbacks) {
+                    callback.postOnShortcutChanged(packageName, user, list);
+                }
+            }
+        }
     };
 
     private static class CallbackMessageHandler extends Handler {
@@ -484,6 +711,7 @@
         private static final int MSG_UNAVAILABLE = 5;
         private static final int MSG_SUSPENDED = 6;
         private static final int MSG_UNSUSPENDED = 7;
+        private static final int MSG_SHORTCUT_CHANGED = 8;
 
         private LauncherApps.Callback mCallback;
 
@@ -492,6 +720,7 @@
             String packageName;
             boolean replacing;
             UserHandle user;
+            List<ShortcutInfo> shortcuts;
         }
 
         public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
@@ -527,6 +756,9 @@
                 case MSG_UNSUSPENDED:
                     mCallback.onPackagesUnsuspended(info.packageNames, info.user);
                     break;
+                case MSG_SHORTCUT_CHANGED:
+                    mCallback.onShortcutsChanged(info.packageName, info.shortcuts, info.user);
+                    break;
             }
         }
 
@@ -582,5 +814,14 @@
             info.user = user;
             obtainMessage(MSG_UNSUSPENDED, info).sendToTarget();
         }
+
+        public void postOnShortcutChanged(String packageName, UserHandle user,
+                List<ShortcutInfo> shortcuts) {
+            CallbackInfo info = new CallbackInfo();
+            info.packageName = packageName;
+            info.user = user;
+            info.shortcuts = shortcuts;
+            obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget();
+        }
     }
 }
diff --git a/core/java/android/content/pm/ShortcutInfo.aidl b/core/java/android/content/pm/ShortcutInfo.aidl
new file mode 100644
index 0000000..08e1873
--- /dev/null
+++ b/core/java/android/content/pm/ShortcutInfo.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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.content.pm;
+
+parcelable ShortcutInfo;
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
new file mode 100644
index 0000000..6520563
--- /dev/null
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -0,0 +1,680 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * TODO Enhance javadoc
+ *
+ * Represents a shortcut form an application.
+ *
+ * Notes...
+ * - If an {@link Icon} is of a resource, then we'll just persist the package name and resource ID.
+ *
+ *   Otherwise, the bitmap will be fetched when it's registered to ShortcutManager, then *shrunk*
+ *   if necessary, and persisted.
+ *
+ *   We will disallow byte[] icons, because they can easily go over binder size limit.
+ *
+ * TODO Move save/load to this class
+ */
+public class ShortcutInfo implements Parcelable {
+    /* @hide */
+    public static final int FLAG_DYNAMIC = 1 << 0;
+
+    /* @hide */
+    public static final int FLAG_PINNED = 1 << 1;
+
+    /* @hide */
+    public static final int FLAG_HAS_ICON_RES = 1 << 2;
+
+    /* @hide */
+    public static final int FLAG_HAS_ICON_FILE = 1 << 3;
+
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+            FLAG_DYNAMIC,
+            FLAG_PINNED,
+            FLAG_HAS_ICON_RES,
+            FLAG_HAS_ICON_FILE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShortcutFlags {}
+
+    // Cloning options.
+
+    /* @hide */
+    private static final int CLONE_REMOVE_ICON = 1 << 0;
+
+    /* @hide */
+    private static final int CLONE_REMOVE_INTENT = 1 << 1;
+
+    /* @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_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT;
+
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    CLONE_REMOVE_ICON,
+                    CLONE_REMOVE_INTENT,
+                    CLONE_REMOVE_NON_KEY_INFO,
+                    CLONE_REMOVE_FOR_CREATOR,
+                    CLONE_REMOVE_FOR_LAUNCHER
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CloneFlags {}
+
+    private final String mId;
+
+    @NonNull
+    private final String mPackageName;
+
+    @Nullable
+    private ComponentName mActivityComponent;
+
+    @Nullable
+    private Icon mIcon;
+
+    @NonNull
+    private String mTitle;
+
+    @NonNull
+    private Intent mIntent;
+
+    // Internal use only.
+    @NonNull
+    private PersistableBundle mIntentPersistableExtras;
+
+    private int mWeight;
+
+    @Nullable
+    private PersistableBundle mExtras;
+
+    private long mLastChangedTimestamp;
+
+    // Internal use only.
+    @ShortcutFlags
+    private int mFlags;
+
+    // Internal use only.
+    private int mIconResourceId;
+
+    // Internal use only.
+    @Nullable
+    private String mBitmapPath;
+
+    private ShortcutInfo(Builder b) {
+        mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
+
+        // Note we can't do other null checks here because SM.updateShortcuts() takes partial
+        // information.
+        mPackageName = b.mContext.getPackageName();
+        mActivityComponent = b.mActivityComponent;
+        mIcon = b.mIcon;
+        mTitle = b.mTitle;
+        mIntent = b.mIntent;
+        mWeight = b.mWeight;
+        mExtras = b.mExtras;
+        updateTimestamp();
+    }
+
+    /**
+     * Throws if any of the mandatory fields is not set.
+     *
+     * @hide
+     */
+    public void enforceMandatoryFields() {
+        Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided");
+        Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
+    }
+
+    /**
+     * Copy constructor.
+     */
+    private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) {
+        mId = source.mId;
+        mPackageName = source.mPackageName;
+        mActivityComponent = source.mActivityComponent;
+        mFlags = source.mFlags;
+        mLastChangedTimestamp = source.mLastChangedTimestamp;
+
+        if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
+            if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
+                mIcon = source.mIcon;
+            }
+
+            mTitle = source.mTitle;
+            if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
+                mIntent = source.mIntent;
+                mIntentPersistableExtras = source.mIntentPersistableExtras;
+            }
+            mWeight = source.mWeight;
+            mExtras = source.mExtras;
+            mIconResourceId = source.mIconResourceId;
+            mBitmapPath = source.mBitmapPath;
+        }
+    }
+
+    /**
+     * Copy a {@link ShortcutInfo}, optionally removing fields.
+     * @hide
+     */
+    public ShortcutInfo clone(@CloneFlags int cloneFlags) {
+        return new ShortcutInfo(this, cloneFlags);
+    }
+
+    /**
+     * Copy non-null/zero fields from another {@link ShortcutInfo}.  Only "public" information
+     * will be overwritten.  The timestamp will be updated.
+     *
+     * - Flags will not change
+     * - mBitmapPath will not change
+     * - Current time will be set to timestamp
+     *
+     * @hide
+     */
+    public void copyNonNullFieldsFrom(ShortcutInfo source) {
+        Preconditions.checkState(mId == source.mId, "ID must match");
+        Preconditions.checkState(mPackageName.equals(source.mPackageName),
+                "Package namae must match");
+
+        if (source.mActivityComponent != null) {
+            mActivityComponent = source.mActivityComponent;
+        }
+
+        if (source.mIcon != null) {
+            mIcon = source.mIcon;
+        }
+        if (source.mTitle != null) {
+            mTitle = source.mTitle;
+        }
+        if (source.mIntent != null) {
+            mIntent = source.mIntent;
+            mIntentPersistableExtras = source.mIntentPersistableExtras;
+        }
+        if (source.mWeight != 0) {
+            mWeight = source.mWeight;
+        }
+        if (source.mExtras != null) {
+            mExtras = source.mExtras;
+        }
+
+        updateTimestamp();
+    }
+
+    /**
+     * Builder class for {@link ShortcutInfo} objects.
+     */
+    public static class Builder {
+        private final Context mContext;
+
+        private String mId;
+
+        private ComponentName mActivityComponent;
+
+        private Icon mIcon;
+
+        private String mTitle;
+
+        private Intent mIntent;
+
+        private int mWeight;
+
+        private PersistableBundle mExtras;
+
+        /** Constructor. */
+        public Builder(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Sets the ID of the shortcut.  This is a mandatory field.
+         */
+        @NonNull
+        public Builder setId(@NonNull String id) {
+            mId = Preconditions.checkStringNotEmpty(id, "id");
+            return this;
+        }
+
+        /**
+         * Optionally sets the target activity.
+         */
+        @NonNull
+        public Builder setActivityComponent(@NonNull ComponentName activityComponent) {
+            mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent");
+            return this;
+        }
+
+        /**
+         * Optionally sets an icon.
+         *
+         * - Tint is not supported TODO Either check and throw, or support it.
+         * - URI icons will be converted into Bitmap icons at the registration time.
+         *
+         * TODO Only allow Bitmap, Resource and URI types.  byte[] type can easily go over
+         * binder size limit.
+         */
+        @NonNull
+        public Builder setIcon(Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the title of a shortcut.  This is a mandatory field.
+         */
+        @NonNull
+        public Builder setTitle(@NonNull String title) {
+            mTitle = Preconditions.checkStringNotEmpty(title, "title");
+            return this;
+        }
+
+        /**
+         * Sets the intent of a shortcut.  This is a mandatory field.  The extras must only contain
+         * persistable information.  (See {@link PersistableBundle}).
+         */
+        @NonNull
+        public Builder setIntent(@NonNull Intent intent) {
+            mIntent = Preconditions.checkNotNull(intent, "intent");
+            return this;
+        }
+
+        /**
+         * Optionally sets the weight of a shortcut, which will be used by Launcher for sorting.
+         * The larger the weight, the more "important" a shortcut is.
+         */
+        @NonNull
+        public Builder setWeight(int weight) {
+            mWeight = weight;
+            return this;
+        }
+
+        /**
+         * Optional values that application can set.
+         * TODO: reserve keys starting with "android."
+         */
+        @NonNull
+        public Builder setExtras(@NonNull PersistableBundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Creates a {@link ShortcutInfo} instance.
+         */
+        @NonNull
+        public ShortcutInfo build() {
+            return new ShortcutInfo(this);
+        }
+    }
+
+    /**
+     * Return the ID of the shortcut.
+     */
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Return the ID of the shortcut.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Return the target activity, which may be null, in which case the shortcut is not associated
+     * with a specific activity.
+     */
+    @Nullable
+    public ComponentName getActivityComponent() {
+        return mActivityComponent;
+    }
+
+    /**
+     * Icon.
+     *
+     * For performance reasons, this will <b>NOT</b> be available when an instance is returned
+     * by {@link ShortcutManager} or {@link LauncherApps}.  A launcher application needs to use
+     * other APIs in LauncherApps to fetch the bitmap.  TODO Add a precondition for it.
+     *
+     * @hide
+     */
+    @Nullable
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Return the shortcut title.
+     */
+    @NonNull
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Return the intent.
+     * TODO Set mIntentPersistableExtras and before returning.
+     */
+    @NonNull
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /** @hide */
+    @Nullable
+    public PersistableBundle getIntentPersistableExtras() {
+        return mIntentPersistableExtras;
+    }
+
+    /**
+     * Return the weight of a shortcut, which will be used by Launcher for sorting.
+     * The larger the weight, the more "important" a shortcut is.
+     */
+    public int getWeight() {
+        return mWeight;
+    }
+
+    /**
+     * Optional values that application can set.
+     */
+    @Nullable
+    public PersistableBundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Last time when any of the fields was updated.
+     */
+    public long getLastChangedTimestamp() {
+        return mLastChangedTimestamp;
+    }
+
+    /** @hide */
+    @ShortcutFlags
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /** @hide*/
+    public void setFlags(@ShortcutFlags int flags) {
+        mFlags = flags;
+    }
+
+    /** @hide*/
+    public void addFlags(@ShortcutFlags int flags) {
+        mFlags |= flags;
+    }
+
+    /** @hide*/
+    public void clearFlags(@ShortcutFlags int flags) {
+        mFlags &= ~flags;
+    }
+
+    /** @hide*/
+    public boolean hasFlags(@ShortcutFlags int flags) {
+        return (mFlags & flags) == flags;
+    }
+
+    /** Return whether a shortcut is dynamic. */
+    public boolean isDynamic() {
+        return hasFlags(FLAG_DYNAMIC);
+    }
+
+    /** Return whether a shortcut is pinned. */
+    public boolean isPinned() {
+        return hasFlags(FLAG_PINNED);
+    }
+
+    /**
+     * Return whether a shortcut's icon is a resource in the owning package.
+     *
+     * @see LauncherApps#getShortcutIconResId(ShortcutInfo, UserHandle)
+     */
+    public boolean hasIconResource() {
+        return hasFlags(FLAG_HAS_ICON_RES);
+    }
+
+    /**
+     * Return whether a shortcut's icon is stored as a file.
+     *
+     * @see LauncherApps#getShortcutIconFd(ShortcutInfo, UserHandle)
+     */
+    public boolean hasIconFile() {
+        return hasFlags(FLAG_HAS_ICON_FILE);
+    }
+
+    /** @hide */
+    public void updateTimestamp() {
+        mLastChangedTimestamp = System.currentTimeMillis();
+    }
+
+    /** @hide */
+    // VisibleForTesting
+    public void setTimestamp(long value) {
+        mLastChangedTimestamp = value;
+    }
+
+    /** @hide */
+    public void setIcon(Icon icon) {
+        mIcon = icon;
+    }
+
+    /** @hide */
+    public void setTitle(String title) {
+        mTitle = title;
+    }
+
+    /** @hide */
+    public void setIntent(Intent intent) {
+        mIntent = intent;
+    }
+
+    /** @hide */
+    public void setIntentPersistableExtras(PersistableBundle intentPersistableExtras) {
+        mIntentPersistableExtras = intentPersistableExtras;
+    }
+
+    /** @hide */
+    public void setWeight(int weight) {
+        mWeight = weight;
+    }
+
+    /** @hide */
+    public void setExtras(PersistableBundle extras) {
+        mExtras = extras;
+    }
+
+    /** @hide */
+    public int getIconResourceId() {
+        return mIconResourceId;
+    }
+
+    /** @hide */
+    public String getBitmapPath() {
+        return mBitmapPath;
+    }
+
+    /** @hide */
+    public void setBitmapPath(String bitmapPath) {
+        mBitmapPath = bitmapPath;
+    }
+
+    private ShortcutInfo(Parcel source) {
+        final ClassLoader cl = getClass().getClassLoader();
+
+        mId = source.readString();
+        mPackageName = source.readString();
+        mActivityComponent = source.readParcelable(cl);
+        mIcon = source.readParcelable(cl);
+        mTitle = source.readString();
+        mIntent = source.readParcelable(cl);
+        mIntentPersistableExtras = source.readParcelable(cl);
+        mWeight = source.readInt();
+        mExtras = source.readParcelable(cl);
+        mLastChangedTimestamp = source.readLong();
+        mFlags = source.readInt();
+        mIconResourceId = source.readInt();
+        mBitmapPath = source.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeString(mPackageName);
+        dest.writeParcelable(mActivityComponent, flags);
+        dest.writeParcelable(mIcon, flags);
+        dest.writeString(mTitle);
+        dest.writeParcelable(mIntent, flags);
+        dest.writeParcelable(mIntentPersistableExtras, flags);
+        dest.writeInt(mWeight);
+        dest.writeParcelable(mExtras, flags);
+        dest.writeLong(mLastChangedTimestamp);
+        dest.writeInt(mFlags);
+        dest.writeInt(mIconResourceId);
+        dest.writeString(mBitmapPath);
+    }
+
+    public static final Creator<ShortcutInfo> CREATOR =
+            new Creator<ShortcutInfo>() {
+                public ShortcutInfo createFromParcel(Parcel source) {
+                    return new ShortcutInfo(source);
+                }
+                public ShortcutInfo[] newArray(int size) {
+                    return new ShortcutInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Return a string representation, intended for logging.  Some fields will be retracted.
+     */
+    @Override
+    public String toString() {
+        return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
+    }
+
+    /** @hide */
+    public String toInsecureString() {
+        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
+    }
+
+    private String toStringInner(boolean secure, boolean includeInternalData) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("ShortcutInfo {");
+
+        sb.append("id=");
+        sb.append(secure ? "***" : mId);
+
+        sb.append(", packageName=");
+        sb.append(mPackageName);
+
+        if (isDynamic()) {
+            sb.append(", dynamic");
+        }
+        if (isPinned()) {
+            sb.append(", pinned");
+        }
+
+        sb.append(", activity=");
+        sb.append(mActivityComponent);
+
+        sb.append(", title=");
+        sb.append(secure ? "***" : mTitle);
+
+        sb.append(", icon=");
+        sb.append(mIcon);
+
+        sb.append(", weight=");
+        sb.append(mWeight);
+
+        sb.append(", timestamp=");
+        sb.append(mLastChangedTimestamp);
+
+        sb.append(", intent=");
+        sb.append(mIntent);
+
+        sb.append(", intentExtras=");
+        sb.append(secure ? "***" : mIntentPersistableExtras);
+
+        sb.append(", extras=");
+        sb.append(mExtras);
+
+        if (includeInternalData) {
+            sb.append(", flags=");
+            sb.append(mFlags);
+
+            sb.append(", iconRes=");
+            sb.append(mIconResourceId);
+
+            sb.append(", bitmapPath=");
+            sb.append(mBitmapPath);
+        }
+
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /** @hide */
+    public ShortcutInfo(String id, String packageName, ComponentName activityComponent,
+            Icon icon, String title, Intent intent, PersistableBundle intentPersistableExtras,
+            int weight, PersistableBundle extras, long lastChangedTimestamp,
+            int flags, int iconResId, String bitmapPath) {
+        mId = id;
+        mPackageName = packageName;
+        mActivityComponent = activityComponent;
+        mIcon = icon;
+        mTitle = title;
+        mIntent = intent;
+        mIntentPersistableExtras = intentPersistableExtras;
+        mWeight = weight;
+        mExtras = extras;
+        mLastChangedTimestamp = lastChangedTimestamp;
+        mFlags = flags;
+        mIconResourceId = iconResId;
+        mBitmapPath = bitmapPath;
+    }
+}
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
new file mode 100644
index 0000000..4c51d49
--- /dev/null
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -0,0 +1,247 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * TODO Enhance javadoc
+ *
+ * {@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 #addDynamicShortcut(ShortcutInfo)}.  There can be at most
+ * {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same
+ * application.
+ * A dynamic shortcut can be deleted with {@link #deleteDynamicShortcut(String)}, and apps
+ * can also use {@link #deleteAllDynamicShortcuts()} to delete all dynamic shortcuts.
+ *
+ * <p>The shortcuts that are currently published by the above APIs are called "dynamic", because
+ * they can be removed by the creator application at any time.  The user may "pin" dynamic shortcuts
+ * on Launcher to make "pinned" shortcuts.  Pinned shortcuts <b>cannot</b> be removed by the creator
+ * app.  An application can obtain all pinned shortcuts from itself with
+ * {@link #getPinnedShortcuts()}.  Applications should keep the pinned shortcut information
+ * up-to-date using {@link #updateShortcuts(List)}.
+ *
+ * <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
+ * shortcuts.
+ *
+ * <h3>Shortcut IDs</h3>
+ *
+ * Each shortcut must have an ID, which must be unique within each application.  When a shortcut is
+ * published, existing shortcuts with the same ID will be updated.  Note this may include a
+ * pinned shortcut.
+ *
+ * <h3>Rate limiting</h3>
+ *
+ * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcut(ShortcutInfo)},
+ * and {@link #updateShortcuts(List)} will be
+ * rate-limited.  An application can call these methods at most
+ * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset,
+ * which happens at a certain time every day.
+ *
+ * <p>An applications can use {@link #getRateLimitResetTime()} to get the next reset time.
+ *
+ * <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.
+ *
+ * TODO: Define a Broadcast to let apps update shortcuts on a restored device.
+ *
+ * <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)}.
+ */
+public class ShortcutManager {
+    private static final String TAG = "ShortcutManager";
+
+    private final Context mContext;
+    private final IShortcutService mService;
+
+    /**
+     * @hide
+     */
+    public ShortcutManager(Context context, IShortcutService service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Publish a list of shortcuts.  All existing dynamic shortcuts from the caller application
+     * will be replaced.
+     *
+     * <p>This API will be rate-limited.
+     *
+     * @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.
+     */
+    public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+        try {
+            return mService.setDynamicShortcuts(mContext.getPackageName(),
+                    new ParceledListSlice(shortcutInfoList), injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return all dynamic shortcuts from the caller application.  The number of result items
+     * will not exceed the value returned by {@link #getMaxDynamicShortcutCount()}.
+     */
+    @NonNull
+    public List<ShortcutInfo> getDynamicShortcuts() {
+        try {
+            return mService.getDynamicShortcuts(mContext.getPackageName(), injectMyUserId())
+                    .getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Publish a single dynamic shortcut.  If there's already dynamic or pinned shortcuts with
+     * the same ID, they will all be updated.
+     *
+     * <p>This API will be rate-limited.
+     *
+     * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
+     *
+     * @throws IllegalArgumentException if the caller application has already published the
+     * max number of dynamic shortcuts.
+     */
+    public boolean addDynamicShortcut(@NonNull ShortcutInfo shortcutInfo) {
+        try {
+            return mService.addDynamicShortcut(
+                    mContext.getPackageName(), shortcutInfo, injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Delete a single dynamic shortcut by ID.
+     */
+    public void deleteDynamicShortcut(@NonNull String shortcutId) {
+        try {
+            mService.deleteDynamicShortcut(mContext.getPackageName(), shortcutId, injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Delete all dynamic shortcuts from the caller application.
+     */
+    public void deleteAllDynamicShortcuts() {
+        try {
+            mService.deleteAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return all pinned shortcuts from the caller application.
+     */
+    @NonNull
+    public List<ShortcutInfo> getPinnedShortcuts() {
+        try {
+            return mService.getPinnedShortcuts(mContext.getPackageName(), injectMyUserId())
+                    .getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Update all existing shortcuts with the same IDs.  Shortcuts may be pinned and/or dynamic.
+     *
+     * <p>This API will be rate-limited.
+     *
+     * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
+     */
+    public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) {
+        try {
+            return mService.updateShortcuts(mContext.getPackageName(),
+                    new ParceledListSlice(shortcutInfoList), injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the max number of dynamic shortcuts that each application can have at a time.
+     */
+    public int getMaxDynamicShortcutCount() {
+        try {
+            return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the number of times the caller application can call the rate-limited APIs
+     * before the rate limit counter is reset.
+     *
+     * @see #getRateLimitResetTime()
+     */
+    public int getRemainingCallCount() {
+        try {
+            return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return when the rate limit count will be reset next time, in milliseconds since the epoch.
+     *
+     * @see #getRemainingCallCount()
+     * @see System#currentTimeMillis()
+     */
+    public long getRateLimitResetTime() {
+        try {
+            return mService.getRateLimitResetTime(mContext.getPackageName(), injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide injection point */
+    @VisibleForTesting
+    protected int injectMyUserId() {
+        return UserHandle.myUserId();
+    }
+}
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
new file mode 100644
index 0000000..3d6028a
--- /dev/null
+++ b/core/java/android/content/pm/ShortcutServiceInternal.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 android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.LauncherApps.ShortcutQuery;
+
+import java.util.List;
+
+/**
+ * Entry points used by {@link LauncherApps}.
+ *
+ * <p>No permission / argument checks will be performed inside.
+ * Callers must check the calling app permission and the calling package name.
+ * @hide
+ */
+public abstract class ShortcutServiceInternal {
+    public interface ShortcutChangeListener {
+        void onShortcutChanged(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @UserIdInt int userId);
+    }
+
+    public abstract List<ShortcutInfo>
+            getShortcuts(@NonNull String callingPackage, long changedSince,
+            @Nullable String packageName, @Nullable ComponentName componentName,
+            @ShortcutQuery.QueryFlags int flags,
+            int userId);
+
+    public abstract List<ShortcutInfo>
+            getShortcutInfo(@NonNull String callingPackage,
+            @NonNull String packageName, @Nullable List<String> ids, int userId);
+
+    public abstract void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName,
+            @NonNull List<String> shortcutIds, int userId);
+
+    public abstract Intent createShortcutIntent(@NonNull String callingPackage,
+            @NonNull String packageName, @NonNull String shortcutId, int userId);
+
+    public abstract void addListener(@NonNull ShortcutChangeListener listener);
+}
diff --git a/core/java/android/content/res/GradientColor.java b/core/java/android/content/res/GradientColor.java
index 98ef2ea..cc46cbd 100644
--- a/core/java/android/content/res/GradientColor.java
+++ b/core/java/android/content/res/GradientColor.java
@@ -17,6 +17,7 @@
 package android.content.res;
 
 import android.annotation.ColorInt;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources.Theme;
@@ -37,13 +38,48 @@
 import android.util.Xml;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
-
+/**
+ * Lets you define a gradient color, which is used inside
+ * {@link android.graphics.drawable.VectorDrawable}.
+ *
+ * {@link android.content.res.GradientColor}s are created from XML resource files defined in the
+ * "color" subdirectory directory of an application's resource directory.  The XML file contains
+ * a single "gradient" element with a number of attributes and elements inside.  For example:
+ * <pre>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ *   &lt;android:startColor="?android:attr/colorPrimary"/&gt;
+ *   &lt;android:endColor="?android:attr/colorControlActivated"/&gt;
+ *   &lt;.../&gt;
+ *   &lt;android:type="linear"/&gt;
+ * &lt;/gradient&gt;
+ * </pre>
+ *
+ * This can describe either a {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ *
+ * Note that different attributes are relevant for different types of gradient.
+ * For example, android:gradientRadius is only applied to RadialGradient.
+ * androd:centerX and android:centerY are only applied to SweepGradient or RadialGradient.
+ * android:startX, android:startY, android:endX and android:endY are only applied to LinearGradient.
+ *
+ * Also note if any color "item" element is defined, then startColor, centerColor and endColor will
+ * be ignored.
+ */
 public class GradientColor extends ComplexColor {
     private static final String TAG = "GradientColor";
 
     private static final boolean DBG_GRADIENT = false;
 
+    @IntDef({TILE_MODE_CLAMP, TILE_MODE_REPEAT, TILE_MODE_MIRROR})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface GradientTileMode {}
+    private static final int TILE_MODE_CLAMP = 0;
+    private static final int TILE_MODE_REPEAT = 1;
+    private static final int TILE_MODE_MIRROR = 2;
+
     /** Lazily-created factory for this GradientColor. */
     private GradientColorFactory mFactory;
 
@@ -54,7 +90,8 @@
     // all the XML information.
     private Shader mShader = null;
 
-    // Below are the attributes at the root element <gradient>
+    // Below are the attributes at the root element <gradient>.
+    // NOTE: they need to be copied in the copy constructor!
     private int mGradientType = GradientDrawable.LINEAR_GRADIENT;
 
     private float mCenterX = 0f;
@@ -70,6 +107,8 @@
     private int mEndColor = 0;
     private boolean mHasCenterColor = false;
 
+    private int mTileMode = 0; // Clamp mode.
+
     private float mGradientRadius = 0f;
 
     // Below are the attributes for the <item> element.
@@ -100,6 +139,7 @@
             mEndColor = copy.mEndColor;
             mHasCenterColor = copy.mHasCenterColor;
             mGradientRadius = copy.mGradientRadius;
+            mTileMode = copy.mTileMode;
 
             if (copy.mItemColors != null) {
                 mItemColors = copy.mItemColors.clone();
@@ -117,6 +157,20 @@
         }
     }
 
+    // Set the default to clamp mode.
+    private static Shader.TileMode parseTileMode(@GradientTileMode int tileMode) {
+        switch (tileMode) {
+            case TILE_MODE_CLAMP:
+                return Shader.TileMode.CLAMP;
+            case TILE_MODE_REPEAT:
+                return Shader.TileMode.REPEAT;
+            case TILE_MODE_MIRROR:
+                return Shader.TileMode.MIRROR;
+            default:
+                return Shader.TileMode.CLAMP;
+        }
+    }
+
     /**
      * Update the root level's attributes, either for inflate or applyTheme.
      */
@@ -150,6 +204,9 @@
         mEndColor = a.getColor(
                 R.styleable.GradientColor_endColor, mEndColor);
 
+        mTileMode = a.getInt(
+                R.styleable.GradientColor_tileMode, mTileMode);
+
         if (DBG_GRADIENT) {
             Log.v(TAG, "hasCenterColor is " + mHasCenterColor);
             if (mHasCenterColor) {
@@ -157,6 +214,7 @@
             }
             Log.v(TAG, "startColor: " + mStartColor);
             Log.v(TAG, "endColor: " + mEndColor);
+            Log.v(TAG, "tileMode: " + mTileMode);
         }
 
         mGradientRadius = a.getFloat(R.styleable.GradientColor_gradientRadius,
@@ -406,11 +464,11 @@
 
         if (mGradientType == GradientDrawable.LINEAR_GRADIENT) {
             mShader = new LinearGradient(mStartX, mStartY, mEndX, mEndY, tempColors, tempOffsets,
-                    Shader.TileMode.CLAMP);
+                    parseTileMode(mTileMode));
         } else {
             if (mGradientType == GradientDrawable.RADIAL_GRADIENT) {
                 mShader = new RadialGradient(mCenterX, mCenterY, mGradientRadius, tempColors,
-                        tempOffsets, Shader.TileMode.CLAMP);
+                        tempOffsets, parseTileMode(mTileMode));
             } else {
                 mShader = new SweepGradient(mCenterX, mCenterY, tempColors, tempOffsets);
             }
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index ee7bd9a..5c71373 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -235,6 +235,12 @@
         return mParcelledData != null;
     }
 
+    /** @hide */
+    ArrayMap<String, Object> getMap() {
+        unparcel();
+        return mMap;
+    }
+
     /**
      * Returns the number of mappings contained in this Bundle.
      *
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index ea180b2..f36bb29 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -24,9 +24,6 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * A mapping from String values to various types that can be saved to persistent and later
@@ -82,6 +79,18 @@
         super(b);
     }
 
+
+    /**
+     * Constructs a PersistableBundle from a Bundle.
+     *
+     * @param b a Bundle to be copied.
+     *
+     * @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
+     */
+    public PersistableBundle(Bundle b) {
+        this(b.getMap());
+    }
+
     /**
      * Constructs a PersistableBundle containing the mappings passed in.
      *
@@ -101,6 +110,8 @@
             if (value instanceof ArrayMap) {
                 // Fix up any Maps by replacing them with PersistableBundles.
                 mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
+            } else if (value instanceof Bundle) {
+                mMap.setValueAt(i, new PersistableBundle(((Bundle) value)));
             } else if (!isValidType(value)) {
                 throw new IllegalArgumentException("Bad value in PersistableBundle key="
                         + mMap.keyAt(i) + " value=" + value);
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index ff37042..9467c22 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -49,6 +49,7 @@
     private EditText mEditText;
     
     private String mText;
+    private boolean mTextSet;
 
     public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
@@ -85,15 +86,16 @@
      * @param text The text to save
      */
     public void setText(String text) {
-        final boolean wasBlocking = shouldDisableDependents();
-        
-        mText = text;
-        
-        persistString(text);
-        
-        final boolean isBlocking = shouldDisableDependents(); 
-        if (isBlocking != wasBlocking) {
-            notifyDependencyChange(isBlocking);
+        // Always persist/notify the first time.
+        final boolean changed = !TextUtils.equals(mText, text);
+        if (changed || !mTextSet) {
+            mText = text;
+            mTextSet = true;
+            persistString(text);
+            if(changed) {
+                notifyDependencyChange(shouldDisableDependents());
+                notifyChanged();
+            }
         }
     }
     
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 0439fe2..def303a 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -29,11 +29,14 @@
  * <p>
  * The content provider exposes a table containing blocked numbers. The columns and URIs for
  * accessing this table are defined by the {@link BlockedNumbers} class. Messages, and calls from
- * blocked numbers are discarded by the platform. If the user contacts emergency
- * services, number blocking is disabled by the platform for a duration defined by
+ * blocked numbers are discarded by the platform. Notifications upon provider changes can be
+ * received using a {@link android.database.ContentObserver}.
+ * </p>
+ * <p>
+ * The platform will not block messages, and calls from emergency numbers as defined by
+ * {@link android.telephony.PhoneNumberUtils#isEmergencyNumber(String)}. If the user contacts
+ * emergency services, number blocking is disabled by the platform for a duration defined by
  * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}.
- * Notifications upon provider changes can be received using a
- * {@link android.database.ContentObserver}.
  * </p>
  *
  * <h3> Permissions </h3>
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 11737c6..0163b47 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -16,32 +16,56 @@
 
 package android.service.notification;
 
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
+import android.app.AutomaticZenRule;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
- * Condition information from condition providers. Used to tell the system to enter Do Not Disturb
- * mode and request that the system exit Do Not Disturb mode.
+ * The current condition of an {@link android.app.AutomaticZenRule}, provided by the
+ * {@link ConditionProviderService} that owns the rule. Used to tell the system to enter Do Not
+ * Disturb mode and request that the system exit Do Not Disturb mode.
  */
 public class Condition implements Parcelable {
 
+    @SystemApi
     public static final String SCHEME = "condition";
 
+    /** @hide */
+    @IntDef({STATE_FALSE, STATE_TRUE, STATE_TRUE, STATE_ERROR})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {}
+
+    /**
+     * Indicates that Do Not Disturb should be turned off. Note that all Conditions from all
+     * {@link ConditionProviderService} providers must be off for Do Not Disturb to be turned off on
+     * the device.
+     */
     public static final int STATE_FALSE = 0;
+    /**
+     * Indicates that Do Not Disturb should be turned on.
+     */
     public static final int STATE_TRUE = 1;
+
+    @SystemApi
     public static final int STATE_UNKNOWN = 2;
+    @SystemApi
     public static final int STATE_ERROR = 3;
 
+    @SystemApi
     public static final int FLAG_RELEVANT_NOW = 1 << 0;
+    @SystemApi
     public static final int FLAG_RELEVANT_ALWAYS = 1 << 1;
 
     /**
-     * The URI representing the condition being updated.
+     * The URI representing the rule being updated.
      * See {@link android.app.AutomaticZenRule#getConditionId()}.
      */
     public final Uri id;
@@ -52,23 +76,17 @@
      */
     public final String summary;
 
-    /**
-     * Additional information about what the rule encoded in {@link #id} means when it is enabled.
-     * User visible if the state of the condition is {@link #STATE_TRUE}.
-     */
+    @SystemApi
     public final String line1;
-
-    /**
-     * Additional information about what the rule encoded in {@link #id} means when it is enabled.
-     * User visible if the state of the condition is {@link #STATE_TRUE}.
-     */
+    @SystemApi
     public final String line2;
 
     /**
-     * The state of this condition. {@link #STATE_TRUE} will enable Do Not Disturb mode. Any other
-     * state will turn Do Not Disturb off for this rule. Note that Do Not Disturb might still be
-     * enabled globally if other conditions are in a {@link #STATE_TRUE} state.
+     * The state of this condition. {@link #STATE_TRUE} will enable Do Not Disturb mode.
+     * {@link #STATE_FALSE} will turn Do Not Disturb off for this rule. Note that Do Not Disturb
+     * might still be enabled globally if other conditions are in a {@link #STATE_TRUE} state.
      */
+    @State
     public final int state;
 
     @SystemApi
@@ -76,8 +94,13 @@
     @SystemApi
     public final int icon;
 
-    public Condition(Uri id, String summary, String line1, String line2, int state) {
-        this(id, summary, line1, line2, -1, state, FLAG_RELEVANT_ALWAYS);
+    /**
+     * An object representing the current state of a {@link android.app.AutomaticZenRule}.
+     * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
+     * @param summary a user visible description of the rule state.
+     */
+    public Condition(Uri id, String summary, int state) {
+        this(id, summary, "", "", -1, state, FLAG_RELEVANT_ALWAYS);
     }
 
     @SystemApi
@@ -85,8 +108,6 @@
             int state, int flags) {
         if (id == null) throw new IllegalArgumentException("id is required");
         if (summary == null) throw new IllegalArgumentException("summary is required");
-        if (line1 == null) throw new IllegalArgumentException("line1 is required");
-        if (line2 == null) throw new IllegalArgumentException("line2 is required");
         if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state);
         this.id = id;
         this.summary = summary;
@@ -97,7 +118,7 @@
         this.flags = flags;
     }
 
-    private Condition(Parcel source) {
+    public Condition(Parcel source) {
         this((Uri)source.readParcelable(Condition.class.getClassLoader()),
                 source.readString(),
                 source.readString(),
@@ -135,6 +156,7 @@
             .append(']').toString();
     }
 
+    @SystemApi
     public static String stateToString(int state) {
         if (state == STATE_FALSE) return "STATE_FALSE";
         if (state == STATE_TRUE) return "STATE_TRUE";
@@ -143,6 +165,7 @@
         throw new IllegalArgumentException("state is invalid: " + state);
     }
 
+    @SystemApi
     public static String relevanceToString(int flags) {
         final boolean now = (flags & FLAG_RELEVANT_NOW) != 0;
         final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0;
@@ -175,6 +198,7 @@
         return 0;
     }
 
+    @SystemApi
     public Condition copy() {
         final Parcel parcel = Parcel.obtain();
         try {
@@ -186,10 +210,14 @@
         }
     }
 
+    @SystemApi
     public static Uri.Builder newId(Context context) {
-        return new Uri.Builder().scheme(SCHEME).authority(context.getPackageName());
+        return new Uri.Builder()
+                .scheme(Condition.SCHEME)
+                .authority(context.getPackageName());
     }
 
+    @SystemApi
     public static boolean isValidId(Uri id, String pkg) {
         return id != null && SCHEME.equals(id.getScheme()) && pkg.equals(id.getAuthority());
     }
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index adcc9d6..44c3887 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -102,9 +102,6 @@
      */
     abstract public void onConnected();
 
-    /**
-     * @removed
-     */
     @SystemApi
     public void onRequestConditions(int relevance) {}
 
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 4afa9fe..316c7e3 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -41,12 +41,12 @@
 import android.widget.ListView;
 import android.widget.Spinner;
 
+import com.android.internal.R;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.StringTokenizer;
 
-import com.android.internal.R;
-
 /**
  * A Transition holds information about animations that will be run on its
  * targets during a scene change. Subclasses of this abstract class may
@@ -192,7 +192,7 @@
     private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
     private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
     TransitionSet mParent = null;
-    private int[] mMatchOrder = DEFAULT_MATCH_ORDER;
+    int[] mMatchOrder = DEFAULT_MATCH_ORDER;
     ArrayList<TransitionValues> mStartValuesList; // only valid after playTransition starts
     ArrayList<TransitionValues> mEndValuesList; // only valid after playTransitions starts
 
@@ -246,7 +246,7 @@
 
     // The function used to interpolate along two-dimensional points. Typically used
     // for adding curves to x/y View motion.
-    private PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
+    PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
 
     /**
      * Constructs a Transition object with no target objects. A transition with
@@ -780,7 +780,7 @@
                 }
             }
         }
-        if (minStartDelay != 0) {
+        if (startDelays.size() != 0) {
             for (int i = 0; i < startDelays.size(); i++) {
                 int index = startDelays.keyAt(i);
                 Animator animator = mAnimators.get(index);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6e02516..17f1991 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -135,6 +135,18 @@
     }
 
     /**
+     * Message for taking fullscreen screenshot
+     * @hide
+     */
+    final int TAKE_SCREENSHOT_FULLSCREEN = 1;
+
+    /**
+     * Message for taking screenshot of selected region.
+     * @hide
+     */
+    final int TAKE_SCREENSHOT_SELECTED_REGION = 2;
+
+    /**
      * @hide
      */
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
@@ -269,6 +281,7 @@
             @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING, to = "TYPE_VOICE_INTERACTION_STARTING"),
             @ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER, to = "TYPE_DOCK_DIVIDER"),
             @ViewDebug.IntToString(from = TYPE_QS_DIALOG, to = "TYPE_QS_DIALOG"),
+            @ViewDebug.IntToString(from = TYPE_SCREENSHOT, to = "TYPE_SCREENSHOT")
         })
         public int type;
 
@@ -622,6 +635,13 @@
         public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
 
         /**
+         * Window type: shares similar characteristics with {@link #TYPE_DREAM}. The layer is
+         * reserved for screenshot region selection.
+         * @hide
+         */
+        public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index 471b6d4..fc17f7a 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -31,10 +31,12 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.Xml;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
@@ -55,7 +57,7 @@
     /**
      * The array of subtypes.
      */
-    private final ArrayList<SpellCheckerSubtype> mSubtypes = new ArrayList<SpellCheckerSubtype>();
+    private final ArrayList<SpellCheckerSubtype> mSubtypes = new ArrayList<>();
 
     /**
      * Constructor.
@@ -267,4 +269,22 @@
     public int describeContents() {
         return 0;
     }
+
+    /**
+     * @hide
+     */
+    public void dump(final PrintWriter pw, final String prefix) {
+        pw.println(prefix + "mId=" + mId);
+        pw.println(prefix + "mSettingsActivityName=" + mSettingsActivityName);
+        pw.println(prefix + "Service:");
+        mService.dump(new PrintWriterPrinter(pw), prefix + "  ");
+        final int N = getSubtypeCount();
+        for (int i = 0; i < N; i++) {
+            final SpellCheckerSubtype st = getSubtypeAt(i);
+            pw.println(prefix + "  " + "Subtype #" + i + ":");
+            pw.println(prefix + "    " + "locale=" + st.getLocale()
+                    + " languageTag=" + st.getLanguageTag());
+            pw.println(prefix + "    " + "extraValue=" + st.getExtraValue());
+        }
+    }
 }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 90a573b..9ccd7f0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8170,6 +8170,8 @@
              Defined in same coordinates as the path itself -->
         <attr name="endY" format="float" />
 
+        <!-- Defines the tile mode of the gradient. SweepGradient don't support tiling. -->
+        <attr name="tileMode"/>
     </declare-styleable>
 
     <!-- Describes an item of a GradientColor. Minimally need 2 items to define the gradient
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1d7034d..17afd92 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4228,4 +4228,9 @@
     <!-- 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 shown when device has been forced to safe mode after a security compromise. -->
+    <string name="audit_safemode_notification">Factory reset to use this device normally</string>
+    <!-- Description of notification shown when device has been forced to safe mode after a security compromise. -->
+    <string name="audit_safemode_notification_details">Touch to learn more.</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0120128..cad0e7b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1895,6 +1895,8 @@
   <java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
   <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="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
diff --git a/core/tests/coretests/src/android/transition/TransitionTest.java b/core/tests/coretests/src/android/transition/TransitionTest.java
new file mode 100644
index 0000000..7e72e25
--- /dev/null
+++ b/core/tests/coretests/src/android/transition/TransitionTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.transition;
+
+import android.animation.AnimatorSetActivity;
+import android.app.Activity;
+import android.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase2;
+import android.transition.Transition.EpicenterCallback;
+import android.util.ArrayMap;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+public class TransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
+    Activity mActivity;
+    public TransitionTest() {
+        super(AnimatorSetActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        mActivity = getActivity();
+    }
+
+    public void testClone() throws Throwable {
+        View square1 = mActivity.findViewById(R.id.square1);
+        View square2 = mActivity.findViewById(R.id.square2);
+        View square3 = mActivity.findViewById(R.id.square3);
+        Fade fade = new Fade();
+        fade.setStartDelay(1000);
+        fade.setDuration(1001);
+
+        fade.addTarget(square1);
+        fade.excludeTarget(square2, true);
+        fade.excludeChildren(square3, true);
+
+        fade.addTarget(R.id.square4);
+        fade.excludeTarget(R.id.square3, true);
+        fade.excludeChildren(R.id.square2, true);
+
+        fade.addTarget("hello");
+        fade.excludeTarget("world", true);
+
+        fade.addTarget(View.class);
+        fade.excludeTarget(TextView.class, true);
+
+        fade.setMatchOrder(Transition.MATCH_ID);
+        fade.setPropagation(new CircularPropagation());
+        fade.setPathMotion(new ArcMotion());
+        fade.setInterpolator(new AccelerateInterpolator());
+        fade.setNameOverrides(new ArrayMap<>());
+
+        EpicenterCallback epicenterCallback = new EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return null;
+            }
+        };
+
+        fade.setEpicenterCallback(epicenterCallback);
+
+        Fade clone = (Fade) fade.clone();
+        assertEquals(fade.mStartDelay, clone.mStartDelay);
+        assertEquals(fade.mDuration, clone.mDuration);
+        assertEquals(fade.mInterpolator, clone.mInterpolator);
+        assertEquals(fade.mPropagation, clone.mPropagation);
+        assertEquals(fade.getPathMotion(), clone.getPathMotion());
+        assertEquals(fade.getEpicenterCallback(), clone.getEpicenterCallback());
+        assertEquals(fade.mNameOverrides, clone.mNameOverrides);
+        assertEquals(fade.mMatchOrder, clone.mMatchOrder);
+
+        assertEquals(fade.mTargets, clone.mTargets);
+        assertEquals(fade.mTargetExcludes, clone.mTargetExcludes);
+        assertEquals(fade.mTargetChildExcludes, clone.mTargetChildExcludes);
+
+        assertEquals(fade.mTargetIds, clone.mTargetIds);
+        assertEquals(fade.mTargetIdExcludes, clone.mTargetIdExcludes);
+        assertEquals(fade.mTargetIdChildExcludes, clone.mTargetIdChildExcludes);
+
+        assertEquals(fade.mTargetNames, clone.mTargetNames);
+        assertEquals(fade.mTargetNameExcludes, clone.mTargetNameExcludes);
+
+        assertEquals(fade.mTargetTypes, clone.mTargetTypes);
+        assertEquals(fade.mTargetTypeExcludes, clone.mTargetTypeExcludes);
+    }
+}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 70995ac..9a9a89b 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -227,15 +227,16 @@
 LOCAL_MODULE := hwui_unit_tests
 LOCAL_MODULE_TAGS := tests
 LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
+LOCAL_SHARED_LIBRARIES := libmemunreachable
 LOCAL_CFLAGS := \
         $(hwui_cflags) \
         -DHWUI_NULL_GPU
 
 LOCAL_SRC_FILES += \
     $(hwui_test_common_src_files) \
+    tests/unit/main.cpp \
     tests/unit/CanvasStateTests.cpp \
     tests/unit/ClipAreaTests.cpp \
-    tests/unit/CrashHandlerInjector.cpp \
     tests/unit/DamageAccumulatorTests.cpp \
     tests/unit/DeviceInfoTests.cpp \
     tests/unit/FatVectorTests.cpp \
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 9595a85..c809ff4 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,9 +19,6 @@
 #include "DeferredLayerUpdater.h"
 #include "LayerRenderer.h"
 
-#include <unistd.h>
-#include <signal.h>
-
 namespace android {
 namespace uirenderer {
 
@@ -136,27 +133,7 @@
     canvas->drawTextOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint);
 }
 
-static void defaultCrashHandler() {
-    fprintf(stderr, "RenderThread crashed!");
-}
-
-static std::function<void()> gCrashHandler = defaultCrashHandler;
-static sighandler_t gPreviousSignalHandler;
-
-static void signalHandler(int sig) {
-    gCrashHandler();
-    if (gPreviousSignalHandler) {
-        gPreviousSignalHandler(sig);
-    }
-}
-
-void TestUtils::setRenderThreadCrashHandler(std::function<void()> crashHandler) {
-    gCrashHandler = crashHandler;
-}
-
 void TestUtils::TestTask::run() {
-    gPreviousSignalHandler = signal(SIGABRT, signalHandler);
-
     // RenderState only valid once RenderThread is running, so queried here
     RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
 
@@ -164,9 +141,6 @@
     rtCallback(renderthread::RenderThread::getInstance());
     renderState.flush(Caches::FlushMode::Full);
     renderState.onGLContextDestroyed();
-
-    // Restore the previous signal handler
-    signal(SIGABRT, gPreviousSignalHandler);
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 6f23705..28ac116 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -180,8 +180,6 @@
 
     typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
 
-    static void setRenderThreadCrashHandler(std::function<void()> crashHandler);
-
     class TestTask : public renderthread::RenderTask {
     public:
         TestTask(RtCallback rtCallback)
diff --git a/libs/hwui/tests/unit/CrashHandlerInjector.cpp b/libs/hwui/tests/unit/CrashHandlerInjector.cpp
deleted file mode 100644
index b1c678d..0000000
--- a/libs/hwui/tests/unit/CrashHandlerInjector.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 "tests/common/TestUtils.h"
-
-#include <gtest/gtest.h>
-#include <cstdio>
-
-using namespace android::uirenderer;
-
-static void gunitCrashHandler() {
-    auto testinfo = ::testing::UnitTest::GetInstance()->current_test_info();
-    printf("[  FAILED  ] %s.%s\n", testinfo->test_case_name(),
-            testinfo->name());
-    printf("[  FATAL!  ] RenderThread crashed, aborting tests!\n");
-    fflush(stdout);
-}
-
-static void hookError() {
-    TestUtils::setRenderThreadCrashHandler(gunitCrashHandler);
-}
-
-class HookErrorInit {
-public:
-    HookErrorInit() { hookError(); }
-};
-
-static HookErrorInit sInit;
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
new file mode 100644
index 0000000..409a12d
--- /dev/null
+++ b/libs/hwui/tests/unit/main.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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 "gtest/gtest.h"
+
+#include "Caches.h"
+#include "thread/TaskManager.h"
+#include "tests/common/TestUtils.h"
+
+#include <memunreachable/memunreachable.h>
+
+#include <cstdio>
+#include <iostream>
+#include <map>
+#include <unordered_set>
+#include <signal.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace android;
+using namespace android::uirenderer;
+
+static auto CRASH_SIGNALS = {
+        SIGABRT,
+        SIGSEGV,
+        SIGBUS,
+};
+
+static map<int, struct sigaction> gSigChain;
+
+static void gtestSigHandler(int sig, siginfo_t* siginfo, void* context) {
+    auto testinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+    printf("[  FAILED  ] %s.%s\n", testinfo->test_case_name(),
+            testinfo->name());
+    printf("[  FATAL!  ] Process crashed, aborting tests!\n");
+    fflush(stdout);
+
+    // restore the default sighandler and re-raise
+    struct sigaction sa = gSigChain[sig];
+    sigaction(sig, &sa, nullptr);
+    raise(sig);
+}
+
+static void logUnreachable(initializer_list<UnreachableMemoryInfo> infolist) {
+    // merge them all
+    UnreachableMemoryInfo merged;
+    unordered_set<uintptr_t> addrs;
+    merged.allocation_bytes = 0;
+    merged.leak_bytes = 0;
+    merged.num_allocations = 0;
+    merged.num_leaks = 0;
+    for (auto& info : infolist) {
+        // We'll be a little hazzy about these ones and just hope the biggest
+        // is the most accurate
+        merged.allocation_bytes = max(merged.allocation_bytes, info.allocation_bytes);
+        merged.num_allocations = max(merged.num_allocations, info.num_allocations);
+        for (auto& leak : info.leaks) {
+             if (addrs.find(leak.begin) == addrs.end()) {
+                 merged.leaks.push_back(leak);
+                 merged.num_leaks++;
+                 merged.leak_bytes += leak.size;
+                 addrs.insert(leak.begin);
+             }
+        }
+    }
+
+    // Now log the result
+    if (merged.num_leaks) {
+        cout << endl << "Leaked memory!" << endl;
+        if (!merged.leaks[0].backtrace.num_frames) {
+            cout << "Re-run with 'setprop libc.debug.malloc.program hwui_unit_test'"
+                    << endl << "and 'setprop libc.debug.malloc.options backtrace=8'"
+                    << " to get backtraces" << endl;
+        }
+        cout << merged.ToString(false);
+    }
+}
+
+static void checkForLeaks() {
+    // TODO: Until we can shutdown the RT thread we need to do this in
+    // two passes as GetUnreachableMemory has limited insight into
+    // thread-local caches so some leaks will not be properly tagged as leaks
+    nsecs_t before = systemTime();
+    UnreachableMemoryInfo rtMemInfo;
+    TestUtils::runOnRenderThread([&rtMemInfo](renderthread::RenderThread& thread) {
+        if (Caches::hasInstance()) {
+            Caches::getInstance().tasks.stop();
+        }
+        // Check for leaks
+        if (!GetUnreachableMemory(rtMemInfo)) {
+            cerr << "Failed to get unreachable memory!" << endl;
+            return;
+        }
+    });
+    UnreachableMemoryInfo uiMemInfo;
+    if (!GetUnreachableMemory(uiMemInfo)) {
+        cerr << "Failed to get unreachable memory!" << endl;
+        return;
+    }
+    logUnreachable({rtMemInfo, uiMemInfo});
+    nsecs_t after = systemTime();
+    cout << "Leak check took " << ns2ms(after - before) << "ms" << endl;
+}
+
+int main(int argc, char* argv[]) {
+    // Register a crash handler
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_sigaction = &gtestSigHandler;
+    sa.sa_flags = SA_SIGINFO;
+    for (auto sig : CRASH_SIGNALS) {
+        struct sigaction old_sa;
+        sigaction(sig, &sa, &old_sa);
+        gSigChain.insert(pair<int, struct sigaction>(sig, old_sa));
+    }
+
+    // Run the tests
+    testing::InitGoogleTest(&argc, argv);
+    int ret = RUN_ALL_TESTS();
+    checkForLeaks();
+    return ret;
+}
+
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index b8f0717..3771474 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1738,7 +1738,10 @@
     // TODO remove: implementation calls directly into implementation of native_release()
     private native final void native_finalize();
 
-    private native final void native_release();
+    /**
+     * @hide
+     */
+    public native final void native_release();
 
     private native final int native_start(int syncEvent, int sessionId);
 
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index c48bfc5..2aac2b3 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2794,7 +2794,10 @@
 
     private native final void native_finalize();
 
-    private native final void native_release();
+    /**
+     * @hide
+     */
+    public native final void native_release();
 
     private native final void native_start();
 
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index c08f4bf..81cc035 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -335,7 +335,6 @@
 
             switch (status) {
                 case ACQUIRE_SUCCESS:
-                    si.createSurfacePlanes();
                     si.mIsImageValid = true;
                 case ACQUIRE_NO_BUFS:
                 case ACQUIRE_MAX_IMAGES:
@@ -693,7 +692,7 @@
                     width = ImageReader.this.getWidth();
                     break;
                 default:
-                    width = nativeGetWidth(mFormat);
+                    width = nativeGetWidth();
             }
             return width;
         }
@@ -709,7 +708,7 @@
                     height = ImageReader.this.getHeight();
                     break;
                 default:
-                    height = nativeGetHeight(mFormat);
+                    height = nativeGetHeight();
             }
             return height;
         }
@@ -729,6 +728,10 @@
         @Override
         public Plane[] getPlanes() {
             throwISEIfImageIsInvalid();
+
+            if (mPlanes == null) {
+                mPlanes = nativeCreatePlanes(ImageReader.this.mNumPlanes, ImageReader.this.mFormat);
+            }
             // Shallow copy is fine.
             return mPlanes.clone();
         }
@@ -766,7 +769,8 @@
         }
 
         private void clearSurfacePlanes() {
-            if (mIsImageValid) {
+            // Image#getPlanes may not be called before the image is closed.
+            if (mIsImageValid && mPlanes != null) {
                 for (int i = 0; i < mPlanes.length; i++) {
                     if (mPlanes[i] != null) {
                         mPlanes[i].clearBuffer();
@@ -776,32 +780,25 @@
             }
         }
 
-        private void createSurfacePlanes() {
-            mPlanes = new SurfacePlane[ImageReader.this.mNumPlanes];
-            for (int i = 0; i < ImageReader.this.mNumPlanes; i++) {
-                mPlanes[i] = nativeCreatePlane(i, ImageReader.this.mFormat);
-            }
-        }
         private class SurfacePlane extends android.media.Image.Plane {
-            // SurfacePlane instance is created by native code when a new SurfaceImage is created
-            private SurfacePlane(int index, int rowStride, int pixelStride) {
-                mIndex = index;
+            // SurfacePlane instance is created by native code when SurfaceImage#getPlanes() is
+            // called
+            private SurfacePlane(int rowStride, int pixelStride, ByteBuffer buffer) {
                 mRowStride = rowStride;
                 mPixelStride = pixelStride;
+                mBuffer = buffer;
+                /**
+                 * Set the byteBuffer order according to host endianness (native
+                 * order), otherwise, the byteBuffer order defaults to
+                 * ByteOrder.BIG_ENDIAN.
+                 */
+                mBuffer.order(ByteOrder.nativeOrder());
             }
 
             @Override
             public ByteBuffer getBuffer() {
-                SurfaceImage.this.throwISEIfImageIsInvalid();
-                if (mBuffer != null) {
-                    return mBuffer;
-                } else {
-                    mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex,
-                            ImageReader.this.mFormat);
-                    // Set the byteBuffer order according to host endianness (native order),
-                    // otherwise, the byteBuffer order defaults to ByteOrder.BIG_ENDIAN.
-                    return mBuffer.order(ByteOrder.nativeOrder());
-                }
+                throwISEIfImageIsInvalid();
+                return mBuffer;
             }
 
             @Override
@@ -837,7 +834,6 @@
                 mBuffer = null;
             }
 
-            final private int mIndex;
             final private int mPixelStride;
             final private int mRowStride;
 
@@ -860,10 +856,10 @@
         // If this image is detached from the ImageReader.
         private AtomicBoolean mIsDetached = new AtomicBoolean(false);
 
-        private synchronized native ByteBuffer nativeImageGetBuffer(int idx, int readerFormat);
-        private synchronized native SurfacePlane nativeCreatePlane(int idx, int readerFormat);
-        private synchronized native int nativeGetWidth(int format);
-        private synchronized native int nativeGetHeight(int format);
+        private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes,
+                int readerFormat);
+        private synchronized native int nativeGetWidth();
+        private synchronized native int nativeGetHeight();
         private synchronized native int nativeGetFormat(int readerFormat);
     }
 
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 851d436..83a4f17 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -748,8 +748,8 @@
             final private int mPixelStride;
             final private int mRowStride;
 
-            // SurfacePlane instance is created by native code when a new
-            // SurfaceImage is created
+            // SurfacePlane instance is created by native code when SurfaceImage#getPlanes() is
+            // called
             private SurfacePlane(int rowStride, int pixelStride, ByteBuffer buffer) {
                 mRowStride = rowStride;
                 mPixelStride = pixelStride;
@@ -795,7 +795,7 @@
 
         }
 
-        // this will create the SurfacePlane object and fill the information
+        // Create the SurfacePlane object and fill the information
         private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes, int writerFmt);
 
         private synchronized native int nativeGetWidth();
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index a332195..e8c50e3 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -575,16 +575,11 @@
         /**
          * The original network ID of this TV channel.
          *
-         * <p>This is used to identify the originating delivery system, if applicable. Use the same
-         * coding for {@code original_network_id} in the underlying broadcast standard if it is
-         * defined there (e.g. ETSI EN 300 468/TR 101 211 and ARIB STD-B10). If channels cannot be
-         * globally identified by 2-tuple {{@link #COLUMN_TRANSPORT_STREAM_ID},
-         * {@link #COLUMN_SERVICE_ID}}, one must carefully assign a value to this field to form a
-         * unique 3-tuple identification {{@code COLUMN_ORIGINAL_NETWORK_ID},
-         * {@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}} for its channels.
+         * <p>It is used to identify the originating delivery system, if applicable. Use the same
+         * coding for {@code original_network_id} for ETSI EN 300 468/TR 101 211 and ARIB STD-B10.
          *
-         * <p>This is a required field if the channel cannot be uniquely identified by a 2-tuple
-         * {{@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}}.
+         * <p>This is a required field only if the underlying broadcast standard defines the same
+         * name field. Otherwise, leave empty.
          *
          * <p>Type: INTEGER
          */
@@ -593,13 +588,13 @@
         /**
          * The transport stream ID of this channel.
          *
-         * <p>This is used to identify the Transport Stream that contains the current channel from
-         * any other multiplex within a network, if applicable. Use the same coding for
+         * <p>It is used to identify the Transport Stream that contains the current channel from any
+         * other multiplex within a network, if applicable. Use the same coding for
          * {@code transport_stream_id} defined in ISO/IEC 13818-1 if the channel is transmitted via
-         * the MPEG Transport Stream as is the case for many digital broadcast standards.
+         * the MPEG Transport Stream.
          *
-         * <p>This is a required field if the current channel is transmitted via the MPEG Transport
-         * Stream.
+         * <p>This is a required field only if the current channel is transmitted via the MPEG
+         * Transport Stream. Leave empty otherwise.
          *
          * <p>Type: INTEGER
          */
@@ -608,15 +603,13 @@
         /**
          * The service ID of this channel.
          *
-         * <p>This is used to identify the current service (roughly equivalent to channel) from any
-         * other service within the Transport Stream, if applicable. Use the same coding for
-         * {@code service_id} in the underlying broadcast standard if it is defined there (e.g. ETSI
-         * EN 300 468 and ARIB STD-B10) or {@code program_number} (which usually has the same value
-         * as {@code service_id}) in ISO/IEC 13818-1 if the channel is transmitted via the MPEG
-         * Transport Stream.
+         * <p>It is used to identify the current service, or channel from any other services within
+         * a given Transport Stream, if applicable. Use the same coding for {@code service_id} in
+         * ETSI EN 300 468 and ARIB STD-B10 or {@code program_number} in ISO/IEC 13818-1.
          *
-         * <p>This is a required field if the current channel is transmitted via the MPEG Transport
-         * Stream.
+         * <p>This is a required field only if the underlying broadcast standard defines the same
+         * name field, or the current channel is transmitted via the MPEG Transport Stream. Leave
+         * empty otherwise.
          *
          * <p>Type: INTEGER
          */
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 8fb58b5..bc20c17 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -491,7 +491,7 @@
          * until this method is called.
          *
          * <p>The TV input service must call this method as soon as the content rendered onto its
-         * surface is ready for viewing. This method must be called each time {@link #onTune(Uri)}
+         * surface is ready for viewing. This method must be called each time {@link #onTune}
          * is called.
          *
          * @see #notifyVideoUnavailable
@@ -837,14 +837,15 @@
         public abstract boolean onTune(Uri channelUri);
 
         /**
-         * Calls {@link #onTune(Uri)}. Override this method in order to handle {@code params}.
+         * Calls {@link #onTune(Uri)}. Override this method in order to handle domain-specific
+         * features that are only known between certain TV inputs and their clients.
          *
          * @param channelUri The URI of the channel.
-         * @param params The extra parameters from other applications.
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
          * @return {@code true} if the tuning was successful, {@code false} otherwise.
-         * @hide
          */
-        @SystemApi
         public boolean onTune(Uri channelUri, Bundle params) {
             return onTune(channelUri);
         }
@@ -1209,7 +1210,7 @@
         }
 
         /**
-         * Calls {@link #onTune}.
+         * Calls {@link #onTune(Uri, Bundle)}.
          */
         void tune(Uri channelUri, Bundle params) {
             mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
@@ -1836,7 +1837,7 @@
      * a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so
      * that the user can see the screen of the hardware TV Input when she tunes to a channel from
      * this TV input. The implementation of this class is expected to change the channel of the
-     * external set-top box via a proprietary protocol when {@link HardwareSession#onTune(Uri)} is
+     * external set-top box via a proprietary protocol when {@link HardwareSession#onTune} is
      * requested by the application.
      *
      * <p>Note that this class is not for inputs for internal hardware like built-in tuner and HDMI
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 5c4b528..9623076 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -56,7 +57,7 @@
  * TV inputs available on the system can be obtained by calling
  * {@link TvInputManager#getTvInputList() TvInputManager.getTvInputList()}.)
  *
- * <p>Once the application supplies the URI for a specific TV channel to {@link #tune(String, Uri)}
+ * <p>Once the application supplies the URI for a specific TV channel to {@link #tune}
  * method, it takes care of underlying service binding (and unbinding if the current TvView is
  * already bound to a service) and automatically allocates/deallocates resources needed. In addition
  * to a few essential methods to control how the contents are presented, it also provides a way to
@@ -206,13 +207,18 @@
     }
 
     /**
-     * Sets the Z order of a window owning the surface of this TvView above the normal TvView
-     * but below an application.
+     * Controls whether the TvView's surface is placed on top of another regular surface view in the
+     * window (but still behind the window itself).
+     * This is typically used to place overlays on top of an underlying TvView.
      *
-     * @see SurfaceView#setZOrderMediaOverlay
-     * @hide
+     * <p>Note that this must be set before the TvView's containing window is attached to the
+     * window manager.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+     *
+     * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false}
+     *            otherwise.
      */
-    @SystemApi
     public void setZOrderMediaOverlay(boolean isMediaOverlay) {
         if (isMediaOverlay) {
             mWindowZOrder = ZORDER_MEDIA_OVERLAY;
@@ -230,12 +236,18 @@
     }
 
     /**
-     * Sets the Z order of a window owning the surface of this TvView on top of an application.
+     * Controls whether the TvView's surface is placed on top of its window. Normally it is placed
+     * behind the window, to allow it to (for the most part) appear to composite with the views in
+     * the hierarchy.  By setting this, you cause it to be placed above the window. This means that
+     * none of the contents of the window this TvView is in will be visible on top of its surface.
      *
-     * @see SurfaceView#setZOrderOnTop
-     * @hide
+     * <p>Note that this must be set before the TvView's containing window is attached to the window
+     * manager.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+     *
+     * @param onTop {@code true} to be on top of its window, {@code false} otherwise.
      */
-    @SystemApi
     public void setZOrderOnTop(boolean onTop) {
         if (onTop) {
             mWindowZOrder = ZORDER_ON_TOP;
@@ -280,14 +292,15 @@
     }
 
     /**
-     * Tunes to a given channel.
+     * Tunes to a given channel. This can be used to provide domain-specific features that are only
+     * known between certain TvView applications and their TV inputs.
      *
      * @param inputId The ID of TV input for the given channel.
      * @param channelUri The URI of a channel.
-     * @param params Extra parameters.
-     * @hide
+     * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+     *            name, i.e. prefixed with a package name you own, so that different developers will
+     *            not create conflicting keys.
      */
-    @SystemApi
     public void tune(String inputId, Uri channelUri, Bundle params) {
         if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")");
         if (TextUtils.isEmpty(inputId)) {
@@ -360,11 +373,8 @@
      *
      * @param unblockedRating A TvContentRating to unblock.
      * @see TvInputService.Session#notifyContentBlocked(TvContentRating)
-     * @hide
-     * @deprecated Use {@link #unblockContent} instead.
+     * @removed
      */
-    @Deprecated
-    @SystemApi
     public void requestUnblockContent(TvContentRating unblockedRating) {
         unblockContent(unblockedRating);
     }
@@ -379,6 +389,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
     public void unblockContent(TvContentRating unblockedRating) {
         if (mSession != null) {
             mSession.unblockContent(unblockedRating);
@@ -539,7 +550,7 @@
     }
 
     /**
-     * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)} for the current
+     * Calls {@link TvInputService.Session#onAppPrivateCommand(String, Bundle)} for the current
      * session.
      *
      * @param action The name of the private command to send. This <em>must</em> be a scoped name,
@@ -893,7 +904,7 @@
 
         /**
          * This is invoked when the channel of this TvView is changed by the underlying TV input
-         * without any {@link TvView#tune(String, Uri)} request.
+         * without any {@link TvView#tune} request.
          *
          * @param inputId The ID of the TV input bound to this view.
          * @param channelUri The URI of a channel.
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 9e90a19..c3993ae 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -16,6 +16,7 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ImageReader_JNI"
+#include "android_media_Utils.h"
 #include <utils/Log.h>
 #include <utils/misc.h>
 #include <utils/List.h>
@@ -23,7 +24,6 @@
 
 #include <cstdio>
 
-#include <gui/CpuConsumer.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/Surface.h>
 #include <camera3.h>
@@ -37,8 +37,6 @@
 #include <stdint.h>
 #include <inttypes.h>
 
-#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
-
 #define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID       "mNativeContext"
 #define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID   "mNativeBuffer"
 #define ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID       "mTimestamp"
@@ -47,9 +45,6 @@
 
 using namespace android;
 
-enum {
-    IMAGE_READER_MAX_NUM_PLANES = 3,
-};
 
 enum {
     ACQUIRE_SUCCESS = 0,
@@ -65,6 +60,7 @@
 static struct {
     jfieldID mNativeBuffer;
     jfieldID mTimestamp;
+    jfieldID mPlanes;
 } gSurfaceImageClassInfo;
 
 static struct {
@@ -89,21 +85,12 @@
 
     virtual void onFrameAvailable(const BufferItem& item);
 
-    CpuConsumer::LockedBuffer* getLockedBuffer();
-    void returnLockedBuffer(CpuConsumer::LockedBuffer* buffer);
+    BufferItem* getBufferItem();
+    void returnBufferItem(BufferItem* buffer);
 
-    BufferItem* getOpaqueBuffer();
-    void returnOpaqueBuffer(BufferItem* buffer);
 
-    void setCpuConsumer(const sp<CpuConsumer>& consumer) { mConsumer = consumer; }
-    CpuConsumer* getCpuConsumer() { return mConsumer.get(); }
-
-    void setOpaqueConsumer(const sp<BufferItemConsumer>& consumer) { mOpaqueConsumer = consumer; }
-    BufferItemConsumer* getOpaqueConsumer() { return mOpaqueConsumer.get(); }
-    // This is the only opaque format exposed in the ImageFormat public API.
-    // Note that we do support CPU access for HAL_PIXEL_FORMAT_RAW_OPAQUE
-    // (ImageFormat#RAW_PRIVATE) so it doesn't count as opaque here.
-    bool isOpaque() { return mFormat == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED; }
+    void setBufferConsumer(const sp<BufferItemConsumer>& consumer) { mConsumer = consumer; }
+    BufferItemConsumer* getBufferConsumer() { return mConsumer.get(); }
 
     void setProducer(const sp<IGraphicBufferProducer>& producer) { mProducer = producer; }
     IGraphicBufferProducer* getProducer() { return mProducer.get(); }
@@ -124,10 +111,8 @@
     static JNIEnv* getJNIEnv(bool* needsDetach);
     static void detachJNI();
 
-    List<CpuConsumer::LockedBuffer*> mBuffers;
-    List<BufferItem*> mOpaqueBuffers;
-    sp<CpuConsumer> mConsumer;
-    sp<BufferItemConsumer> mOpaqueConsumer;
+    List<BufferItem*> mBuffers;
+    sp<BufferItemConsumer> mConsumer;
     sp<IGraphicBufferProducer> mProducer;
     jobject mWeakThiz;
     jclass mClazz;
@@ -140,12 +125,14 @@
 JNIImageReaderContext::JNIImageReaderContext(JNIEnv* env,
         jobject weakThiz, jclass clazz, int maxImages) :
     mWeakThiz(env->NewGlobalRef(weakThiz)),
-    mClazz((jclass)env->NewGlobalRef(clazz)) {
+    mClazz((jclass)env->NewGlobalRef(clazz)),
+    mFormat(0),
+    mDataSpace(HAL_DATASPACE_UNKNOWN),
+    mWidth(-1),
+    mHeight(-1) {
     for (int i = 0; i < maxImages; i++) {
-        CpuConsumer::LockedBuffer *buffer = new CpuConsumer::LockedBuffer;
-        BufferItem* opaqueBuffer = new BufferItem;
+        BufferItem* buffer = new BufferItem;
         mBuffers.push_back(buffer);
-        mOpaqueBuffers.push_back(opaqueBuffer);
     }
 }
 
@@ -174,36 +161,21 @@
     }
 }
 
-CpuConsumer::LockedBuffer* JNIImageReaderContext::getLockedBuffer() {
+BufferItem* JNIImageReaderContext::getBufferItem() {
     if (mBuffers.empty()) {
         return NULL;
     }
-    // Return a LockedBuffer pointer and remove it from the list
-    List<CpuConsumer::LockedBuffer*>::iterator it = mBuffers.begin();
-    CpuConsumer::LockedBuffer* buffer = *it;
+    // Return a BufferItem pointer and remove it from the list
+    List<BufferItem*>::iterator it = mBuffers.begin();
+    BufferItem* buffer = *it;
     mBuffers.erase(it);
     return buffer;
 }
 
-void JNIImageReaderContext::returnLockedBuffer(CpuConsumer::LockedBuffer* buffer) {
+void JNIImageReaderContext::returnBufferItem(BufferItem* buffer) {
     mBuffers.push_back(buffer);
 }
 
-BufferItem* JNIImageReaderContext::getOpaqueBuffer() {
-    if (mOpaqueBuffers.empty()) {
-        return NULL;
-    }
-    // Return an opaque buffer pointer and remove it from the list
-    List<BufferItem*>::iterator it = mOpaqueBuffers.begin();
-    BufferItem* buffer = *it;
-    mOpaqueBuffers.erase(it);
-    return buffer;
-}
-
-void JNIImageReaderContext::returnOpaqueBuffer(BufferItem* buffer) {
-    mOpaqueBuffers.push_back(buffer);
-}
-
 JNIImageReaderContext::~JNIImageReaderContext() {
     bool needsDetach = false;
     JNIEnv* env = getJNIEnv(&needsDetach);
@@ -217,25 +189,15 @@
         detachJNI();
     }
 
-    // Delete LockedBuffers
-    for (List<CpuConsumer::LockedBuffer *>::iterator it = mBuffers.begin();
+    // Delete buffer items.
+    for (List<BufferItem *>::iterator it = mBuffers.begin();
             it != mBuffers.end(); it++) {
         delete *it;
     }
 
-    // Delete opaque buffers
-    for (List<BufferItem *>::iterator it = mOpaqueBuffers.begin();
-            it != mOpaqueBuffers.end(); it++) {
-        delete *it;
-    }
-
-    mBuffers.clear();
     if (mConsumer != 0) {
         mConsumer.clear();
     }
-    if (mOpaqueConsumer != 0) {
-        mOpaqueConsumer.clear();
-    }
 }
 
 void JNIImageReaderContext::onFrameAvailable(const BufferItem& /*item*/)
@@ -257,11 +219,6 @@
 
 extern "C" {
 
-static bool isFormatOpaque(int format) {
-    // Only treat IMPLEMENTATION_DEFINED as an opaque format for now.
-    return format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
-}
-
 static JNIImageReaderContext* ImageReader_getContext(JNIEnv* env, jobject thiz)
 {
     JNIImageReaderContext *ctx;
@@ -270,24 +227,6 @@
     return ctx;
 }
 
-static CpuConsumer* ImageReader_getCpuConsumer(JNIEnv* env, jobject thiz)
-{
-    ALOGV("%s:", __FUNCTION__);
-    JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz);
-    if (ctx == NULL) {
-        jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
-        return NULL;
-    }
-
-    if (ctx->isOpaque()) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                "Opaque ImageReader doesn't support this method");
-        return NULL;
-    }
-
-    return ctx->getCpuConsumer();
-}
-
 static IGraphicBufferProducer* ImageReader_getProducer(JNIEnv* env, jobject thiz)
 {
     ALOGV("%s:", __FUNCTION__);
@@ -315,411 +254,7 @@
             reinterpret_cast<jlong>(ctx.get()));
 }
 
-static CpuConsumer::LockedBuffer* Image_getLockedBuffer(JNIEnv* env, jobject image)
-{
-    return reinterpret_cast<CpuConsumer::LockedBuffer*>(
-            env->GetLongField(image, gSurfaceImageClassInfo.mNativeBuffer));
-}
-
-static void Image_setBuffer(JNIEnv* env, jobject thiz,
-        const CpuConsumer::LockedBuffer* buffer)
-{
-    env->SetLongField(thiz, gSurfaceImageClassInfo.mNativeBuffer, reinterpret_cast<jlong>(buffer));
-}
-
-static void Image_setOpaqueBuffer(JNIEnv* env, jobject thiz,
-        const BufferItem* buffer)
-{
-    env->SetLongField(thiz, gSurfaceImageClassInfo.mNativeBuffer, reinterpret_cast<jlong>(buffer));
-}
-
-static uint32_t Image_getJpegSize(CpuConsumer::LockedBuffer* buffer, bool usingRGBAOverride)
-{
-    ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
-    uint32_t size = 0;
-    uint32_t width = buffer->width;
-    uint8_t* jpegBuffer = buffer->data;
-
-    if (usingRGBAOverride) {
-        width = (buffer->width + buffer->stride * (buffer->height - 1)) * 4;
-    }
-
-    // First check for JPEG transport header at the end of the buffer
-    uint8_t* header = jpegBuffer + (width - sizeof(struct camera3_jpeg_blob));
-    struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header);
-    if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID) {
-        size = blob->jpeg_size;
-        ALOGV("%s: Jpeg size = %d", __FUNCTION__, size);
-    }
-
-    // failed to find size, default to whole buffer
-    if (size == 0) {
-        /*
-         * This is a problem because not including the JPEG header
-         * means that in certain rare situations a regular JPEG blob
-         * will be misidentified as having a header, in which case
-         * we will get a garbage size value.
-         */
-        ALOGW("%s: No JPEG header detected, defaulting to size=width=%d",
-                __FUNCTION__, width);
-        size = width;
-    }
-
-    return size;
-}
-
-static bool usingRGBAToJpegOverride(int32_t bufferFormat, int32_t readerCtxFormat) {
-    return readerCtxFormat == HAL_PIXEL_FORMAT_BLOB && bufferFormat == HAL_PIXEL_FORMAT_RGBA_8888;
-}
-
-static int32_t applyFormatOverrides(int32_t bufferFormat, int32_t readerCtxFormat)
-{
-    // Using HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers containing JPEGs to get around SW
-    // write limitations for some platforms (b/17379185).
-    if (usingRGBAToJpegOverride(bufferFormat, readerCtxFormat)) {
-        return HAL_PIXEL_FORMAT_BLOB;
-    }
-    return bufferFormat;
-}
-
-static void Image_getLockedBufferInfo(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
-                                uint8_t **base, uint32_t *size, int32_t readerFormat)
-{
-    ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
-    ALOG_ASSERT(base != NULL, "base is NULL!!!");
-    ALOG_ASSERT(size != NULL, "size is NULL!!!");
-    ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0));
-
-    ALOGV("%s: buffer: %p", __FUNCTION__, buffer);
-
-    uint32_t dataSize, ySize, cSize, cStride;
-    uint8_t *cb, *cr;
-    uint8_t *pData = NULL;
-    int bytesPerPixel = 0;
-
-    dataSize = ySize = cSize = cStride = 0;
-    int32_t fmt = buffer->flexFormat;
-
-    bool usingRGBAOverride = usingRGBAToJpegOverride(fmt, readerFormat);
-    fmt = applyFormatOverrides(fmt, readerFormat);
-    switch (fmt) {
-        case HAL_PIXEL_FORMAT_YCbCr_420_888:
-            pData =
-                (idx == 0) ?
-                    buffer->data :
-                (idx == 1) ?
-                    buffer->dataCb :
-                buffer->dataCr;
-            // only map until last pixel
-            if (idx == 0) {
-                dataSize = buffer->stride * (buffer->height - 1) + buffer->width;
-            } else {
-                dataSize = buffer->chromaStride * (buffer->height / 2 - 1) +
-                        buffer->chromaStep * (buffer->width / 2 - 1) + 1;
-            }
-            break;
-        // NV21
-        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
-            cr = buffer->data + (buffer->stride * buffer->height);
-            cb = cr + 1;
-            // only map until last pixel
-            ySize = buffer->width * (buffer->height - 1) + buffer->width;
-            cSize = buffer->width * (buffer->height / 2 - 1) + buffer->width - 1;
-
-            pData =
-                (idx == 0) ?
-                    buffer->data :
-                (idx == 1) ?
-                    cb:
-                cr;
-
-            dataSize = (idx == 0) ? ySize : cSize;
-            break;
-        case HAL_PIXEL_FORMAT_YV12:
-            // Y and C stride need to be 16 pixel aligned.
-            LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
-                                "Stride is not 16 pixel aligned %d", buffer->stride);
-
-            ySize = buffer->stride * buffer->height;
-            cStride = ALIGN(buffer->stride / 2, 16);
-            cr = buffer->data + ySize;
-            cSize = cStride * buffer->height / 2;
-            cb = cr + cSize;
-
-            pData =
-                (idx == 0) ?
-                    buffer->data :
-                (idx == 1) ?
-                    cb :
-                cr;
-            dataSize = (idx == 0) ? ySize : cSize;
-            break;
-        case HAL_PIXEL_FORMAT_Y8:
-            // Single plane, 8bpp.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height;
-            break;
-        case HAL_PIXEL_FORMAT_Y16:
-            bytesPerPixel = 2;
-            // Single plane, 16bpp, strides are specified in pixels, not in bytes
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            break;
-        case HAL_PIXEL_FORMAT_BLOB:
-            // Used for JPEG data, height must be 1, width == size, single plane.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            ALOG_ASSERT(buffer->height == 1,
-                    "JPEG should has height value one but got %d", buffer->height);
-
-            pData = buffer->data;
-            dataSize = Image_getJpegSize(buffer, usingRGBAOverride);
-            break;
-        case HAL_PIXEL_FORMAT_RAW16:
-            // Single plane 16bpp bayer data.
-            bytesPerPixel = 2;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            break;
-        case HAL_PIXEL_FORMAT_RAW_OPAQUE:
-            // Used for RAW_OPAQUE data, height must be 1, width == size, single plane.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            ALOG_ASSERT(buffer->height == 1,
-                    "RAW_PRIVATE should has height value one but got %d", buffer->height);
-            pData = buffer->data;
-            dataSize = buffer->width;
-            break;
-        case HAL_PIXEL_FORMAT_RAW10:
-            // Single plane 10bpp bayer data.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            LOG_ALWAYS_FATAL_IF(buffer->width % 4,
-                                "Width is not multiple of 4 %d", buffer->width);
-            LOG_ALWAYS_FATAL_IF(buffer->height % 2,
-                                "Height is not even %d", buffer->height);
-            LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 10 / 8),
-                                "stride (%d) should be at least %d",
-                                buffer->stride, buffer->width * 10 / 8);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height;
-            break;
-        case HAL_PIXEL_FORMAT_RAW12:
-            // Single plane 10bpp bayer data.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            LOG_ALWAYS_FATAL_IF(buffer->width % 4,
-                                "Width is not multiple of 4 %d", buffer->width);
-            LOG_ALWAYS_FATAL_IF(buffer->height % 2,
-                                "Height is not even %d", buffer->height);
-            LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 12 / 8),
-                                "stride (%d) should be at least %d",
-                                buffer->stride, buffer->width * 12 / 8);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height;
-            break;
-        case HAL_PIXEL_FORMAT_RGBA_8888:
-        case HAL_PIXEL_FORMAT_RGBX_8888:
-            // Single plane, 32bpp.
-            bytesPerPixel = 4;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            break;
-        case HAL_PIXEL_FORMAT_RGB_565:
-            // Single plane, 16bpp.
-            bytesPerPixel = 2;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            break;
-        case HAL_PIXEL_FORMAT_RGB_888:
-            // Single plane, 24bpp.
-            bytesPerPixel = 3;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            break;
-        default:
-            jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
-                                 "Pixel format: 0x%x is unsupported", fmt);
-            break;
-    }
-
-    *base = pData;
-    *size = dataSize;
-}
-
-static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
-        int32_t halReaderFormat)
-{
-    ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
-    ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0), "Index is out of range:%d", idx);
-
-    int pixelStride = 0;
-    ALOG_ASSERT(buffer != NULL, "buffer is NULL");
-
-    int32_t fmt = buffer->flexFormat;
-
-    fmt = applyFormatOverrides(fmt, halReaderFormat);
-
-    switch (fmt) {
-        case HAL_PIXEL_FORMAT_YCbCr_420_888:
-            pixelStride = (idx == 0) ? 1 : buffer->chromaStep;
-            break;
-        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
-            pixelStride = (idx == 0) ? 1 : 2;
-            break;
-        case HAL_PIXEL_FORMAT_Y8:
-            // Single plane 8bpp data.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pixelStride = 1;
-            break;
-        case HAL_PIXEL_FORMAT_YV12:
-            pixelStride = 1;
-            break;
-        case HAL_PIXEL_FORMAT_BLOB:
-        case HAL_PIXEL_FORMAT_RAW10:
-        case HAL_PIXEL_FORMAT_RAW12:
-            // Blob is used for JPEG data, RAW10 and RAW12 is used for 10-bit and 12-bit raw data,
-            // those are single plane data with pixel stride 0 since they don't really have a
-            // well defined pixel stride
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pixelStride = 0;
-            break;
-        case HAL_PIXEL_FORMAT_Y16:
-        case HAL_PIXEL_FORMAT_RAW16:
-        case HAL_PIXEL_FORMAT_RGB_565:
-            // Single plane 16bpp data.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pixelStride = 2;
-            break;
-        case HAL_PIXEL_FORMAT_RGBA_8888:
-        case HAL_PIXEL_FORMAT_RGBX_8888:
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pixelStride = 4;
-            break;
-        case HAL_PIXEL_FORMAT_RGB_888:
-            // Single plane, 24bpp.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pixelStride = 3;
-            break;
-        case HAL_PIXEL_FORMAT_RAW_OPAQUE:
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pixelStride = 0; // RAW OPAQUE doesn't have pixel stride
-            break;
-        default:
-            jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
-                                 "Pixel format: 0x%x is unsupported", fmt);
-            break;
-    }
-
-    return pixelStride;
-}
-
-static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
-        int32_t halReaderFormat)
-{
-    ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
-    ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0));
-
-    int rowStride = 0;
-    ALOG_ASSERT(buffer != NULL, "buffer is NULL");
-
-    int32_t fmt = buffer->flexFormat;
-
-    fmt = applyFormatOverrides(fmt, halReaderFormat);
-
-    switch (fmt) {
-        case HAL_PIXEL_FORMAT_YCbCr_420_888:
-            rowStride = (idx == 0) ? buffer->stride : buffer->chromaStride;
-            break;
-        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
-            rowStride = buffer->width;
-            break;
-        case HAL_PIXEL_FORMAT_YV12:
-            LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
-                                "Stride is not 16 pixel aligned %d", buffer->stride);
-            rowStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
-            break;
-        case HAL_PIXEL_FORMAT_BLOB:
-            // Blob is used for JPEG data. It is single plane and has 0 row stride and
-            // 0 pixel stride
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            rowStride = 0;
-            break;
-        case HAL_PIXEL_FORMAT_RAW10:
-        case HAL_PIXEL_FORMAT_RAW12:
-            // RAW10 and RAW12 are used for 10-bit and 12-bit raw data, they are single plane
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            rowStride = buffer->stride;
-            break;
-        case HAL_PIXEL_FORMAT_Y8:
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
-                                "Stride is not 16 pixel aligned %d", buffer->stride);
-            rowStride = buffer->stride;
-            break;
-        case HAL_PIXEL_FORMAT_Y16:
-        case HAL_PIXEL_FORMAT_RAW16:
-            // In native side, strides are specified in pixels, not in bytes.
-            // Single plane 16bpp bayer data. even width/height,
-            // row stride multiple of 16 pixels (32 bytes)
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
-                                "Stride is not 16 pixel aligned %d", buffer->stride);
-            rowStride = buffer->stride * 2;
-            break;
-        case HAL_PIXEL_FORMAT_RGB_565:
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            rowStride = buffer->stride * 2;
-            break;
-        case HAL_PIXEL_FORMAT_RGBA_8888:
-        case HAL_PIXEL_FORMAT_RGBX_8888:
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            rowStride = buffer->stride * 4;
-            break;
-        case HAL_PIXEL_FORMAT_RGB_888:
-            // Single plane, 24bpp.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            rowStride = buffer->stride * 3;
-            break;
-        case HAL_PIXEL_FORMAT_RAW_OPAQUE:
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            rowStride = 0; // RAW OPAQUE doesn't have row stride
-            break;
-        default:
-            ALOGE("%s Pixel format: 0x%x is unsupported", __FUNCTION__, fmt);
-            jniThrowException(env, "java/lang/UnsupportedOperationException",
-                              "unsupported buffer format");
-          break;
-    }
-
-    return rowStride;
-}
-
-static int Image_getBufferWidth(CpuConsumer::LockedBuffer* buffer) {
-    if (buffer == NULL) return -1;
-
-    if (!buffer->crop.isEmpty()) {
-        return buffer->crop.getWidth();
-    }
-    return buffer->width;
-}
-
-static int Image_getBufferHeight(CpuConsumer::LockedBuffer* buffer) {
-    if (buffer == NULL) return -1;
-
-    if (!buffer->crop.isEmpty()) {
-        return buffer->crop.getHeight();
-    }
-    return buffer->height;
-}
-
-// --------------------------Methods for opaque Image and ImageReader----------
-
-static BufferItemConsumer* ImageReader_getOpaqueConsumer(JNIEnv* env, jobject thiz)
+static BufferItemConsumer* ImageReader_getBufferConsumer(JNIEnv* env, jobject thiz)
 {
     ALOGV("%s:", __FUNCTION__);
     JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz);
@@ -728,40 +263,21 @@
         return NULL;
     }
 
-    if (!ctx->isOpaque()) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                "Non-opaque ImageReader doesn't support this method");
-    }
-
-    return ctx->getOpaqueConsumer();
+    return ctx->getBufferConsumer();
 }
 
-static BufferItem* Image_getOpaqueBuffer(JNIEnv* env, jobject image)
+static void Image_setBufferItem(JNIEnv* env, jobject thiz,
+        const BufferItem* buffer)
+{
+    env->SetLongField(thiz, gSurfaceImageClassInfo.mNativeBuffer, reinterpret_cast<jlong>(buffer));
+}
+
+static BufferItem* Image_getBufferItem(JNIEnv* env, jobject image)
 {
     return reinterpret_cast<BufferItem*>(
             env->GetLongField(image, gSurfaceImageClassInfo.mNativeBuffer));
 }
 
-static int Image_getOpaqueBufferWidth(BufferItem* buffer) {
-    if (buffer == NULL) return -1;
-
-    if (!buffer->mCrop.isEmpty()) {
-        return buffer->mCrop.getWidth();
-    }
-    return buffer->mGraphicBuffer->getWidth();
-}
-
-static int Image_getOpaqueBufferHeight(BufferItem* buffer) {
-    if (buffer == NULL) return -1;
-
-    if (!buffer->mCrop.isEmpty()) {
-        return buffer->mCrop.getHeight();
-    }
-
-    return buffer->mGraphicBuffer->getHeight();
-}
-
-
 
 // ----------------------------------------------------------------------------
 
@@ -784,6 +300,11 @@
                         "can't find android/graphics/ImageReader.%s",
                         ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID);
 
+    gSurfaceImageClassInfo.mPlanes = env->GetFieldID(
+            imageClazz, "mPlanes", "[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;");
+    LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mPlanes == NULL,
+            "can't find android/media/ImageReader$ReaderSurfaceImage.mPlanes");
+
     gImageReaderClassInfo.mNativeContext = env->GetFieldID(
             clazz, ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID, "J");
     LOG_ALWAYS_FATAL_IF(gImageReaderClassInfo.mNativeContext == NULL,
@@ -800,7 +321,7 @@
     // FindClass only gives a local reference of jclass object.
     gSurfacePlaneClassInfo.clazz = (jclass) env->NewGlobalRef(planeClazz);
     gSurfacePlaneClassInfo.ctor = env->GetMethodID(gSurfacePlaneClassInfo.clazz, "<init>",
-            "(Landroid/media/ImageReader$SurfaceImage;III)V");
+            "(Landroid/media/ImageReader$SurfaceImage;IILjava/nio/ByteBuffer;)V");
     LOG_ALWAYS_FATAL_IF(gSurfacePlaneClassInfo.ctor == NULL,
             "Can not find SurfacePlane constructor");
 }
@@ -831,81 +352,52 @@
     sp<IGraphicBufferProducer> gbProducer;
     sp<IGraphicBufferConsumer> gbConsumer;
     BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
-    sp<ConsumerBase> consumer;
-    sp<CpuConsumer> cpuConsumer;
-    sp<BufferItemConsumer> opaqueConsumer;
+    sp<BufferItemConsumer> bufferConsumer;
     String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
             width, height, format, maxImages, getpid(),
             createProcessUniqueId());
+    uint32_t consumerUsage = GRALLOC_USAGE_SW_READ_OFTEN;
+
     if (isFormatOpaque(nativeFormat)) {
         // Use the SW_READ_NEVER usage to tell producer that this format is not for preview or video
         // encoding. The only possibility will be ZSL output.
-        opaqueConsumer =
-                new BufferItemConsumer(gbConsumer, GRALLOC_USAGE_SW_READ_NEVER, maxImages,
-                        /*controlledByApp*/true);
-        if (opaqueConsumer == NULL) {
-            jniThrowRuntimeException(env, "Failed to allocate native opaque consumer");
-            return;
-        }
-        ctx->setOpaqueConsumer(opaqueConsumer);
-        opaqueConsumer->setName(consumerName);
-        consumer = opaqueConsumer;
-    } else {
-        cpuConsumer = new CpuConsumer(gbConsumer, maxImages, /*controlledByApp*/true);
-        // TODO: throw dvm exOutOfMemoryError?
-        if (cpuConsumer == NULL) {
-            jniThrowRuntimeException(env, "Failed to allocate native CpuConsumer");
-            return;
-        }
-        ctx->setCpuConsumer(cpuConsumer);
-        cpuConsumer->setName(consumerName);
-        consumer = cpuConsumer;
+        consumerUsage = GRALLOC_USAGE_SW_READ_NEVER;
     }
+    bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
+            /*controlledByApp*/true);
+    if (bufferConsumer == nullptr) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "Failed to allocate native buffer consumer for format 0x%x", nativeFormat);
+        return;
+    }
+    ctx->setBufferConsumer(bufferConsumer);
+    bufferConsumer->setName(consumerName);
 
     ctx->setProducer(gbProducer);
-    consumer->setFrameAvailableListener(ctx);
+    bufferConsumer->setFrameAvailableListener(ctx);
     ImageReader_setNativeContext(env, thiz, ctx);
     ctx->setBufferFormat(nativeFormat);
     ctx->setBufferDataspace(nativeDataspace);
     ctx->setBufferWidth(width);
     ctx->setBufferHeight(height);
 
-    // Set the width/height/format/dataspace to the CpuConsumer
-    // TODO: below code can be simplified once b/19977701 is fixed.
-    if (isFormatOpaque(nativeFormat)) {
-        res = opaqueConsumer->setDefaultBufferSize(width, height);
-        if (res != OK) {
-            jniThrowException(env, "java/lang/IllegalStateException",
-                              "Failed to set opaque consumer buffer size");
-            return;
-        }
-        res = opaqueConsumer->setDefaultBufferFormat(nativeFormat);
-        if (res != OK) {
-            jniThrowException(env, "java/lang/IllegalStateException",
-                              "Failed to set opaque consumer buffer format");
-        }
-        res = opaqueConsumer->setDefaultBufferDataSpace(nativeDataspace);
-        if (res != OK) {
-            jniThrowException(env, "java/lang/IllegalStateException",
-                              "Failed to set opaque consumer buffer dataSpace");
-        }
-    } else {
-        res = cpuConsumer->setDefaultBufferSize(width, height);
-        if (res != OK) {
-            jniThrowException(env, "java/lang/IllegalStateException",
-                              "Failed to set CpuConsumer buffer size");
-            return;
-        }
-        res = cpuConsumer->setDefaultBufferFormat(nativeFormat);
-        if (res != OK) {
-            jniThrowException(env, "java/lang/IllegalStateException",
-                              "Failed to set CpuConsumer buffer format");
-        }
-        res = cpuConsumer->setDefaultBufferDataSpace(nativeDataspace);
-        if (res != OK) {
-            jniThrowException(env, "java/lang/IllegalStateException",
-                              "Failed to set CpuConsumer buffer dataSpace");
-        }
+    // Set the width/height/format/dataspace to the bufferConsumer.
+    res = bufferConsumer->setDefaultBufferSize(width, height);
+    if (res != OK) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                          "Failed to set buffer consumer default size (%dx%d) for format 0x%x",
+                          width, height, nativeFormat);
+        return;
+    }
+    res = bufferConsumer->setDefaultBufferFormat(nativeFormat);
+    if (res != OK) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                          "Failed to set buffer consumer default format 0x%x", nativeFormat);
+    }
+    res = bufferConsumer->setDefaultBufferDataSpace(nativeDataspace);
+    if (res != OK) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                          "Failed to set buffer consumer default dataSpace 0x%x", nativeDataspace);
     }
 }
 
@@ -919,12 +411,8 @@
         return;
     }
 
-    ConsumerBase* consumer = NULL;
-    if (ctx->isOpaque()) {
-        consumer = ImageReader_getOpaqueConsumer(env, thiz);
-    } else {
-        consumer = ImageReader_getCpuConsumer(env, thiz);
-    }
+    BufferItemConsumer* consumer = NULL;
+    consumer = ImageReader_getBufferConsumer(env, thiz);
 
     if (consumer != NULL) {
         consumer->abandon();
@@ -933,6 +421,39 @@
     ImageReader_setNativeContext(env, thiz, NULL);
 }
 
+static sp<Fence> Image_unlockIfLocked(JNIEnv* env, jobject image) {
+    ALOGV("%s", __FUNCTION__);
+    BufferItem* buffer = Image_getBufferItem(env, image);
+    if (buffer == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "Image is not initialized");
+        return Fence::NO_FENCE;
+    }
+
+    // Is locked?
+    bool wasBufferLocked = false;
+    jobject planes = NULL;
+    if (!isFormatOpaque(buffer->mGraphicBuffer->getPixelFormat())) {
+        planes = env->GetObjectField(image, gSurfaceImageClassInfo.mPlanes);
+    }
+    wasBufferLocked = (planes != NULL);
+    if (wasBufferLocked) {
+        status_t res = OK;
+        int fenceFd = -1;
+        if (wasBufferLocked) {
+            res = buffer->mGraphicBuffer->unlockAsync(&fenceFd);
+            if (res != OK) {
+                jniThrowRuntimeException(env, "unlock buffer failed");
+                return Fence::NO_FENCE;
+            }
+        }
+        sp<Fence> releaseFence = new Fence(fenceFd);
+        return releaseFence;
+        ALOGV("Successfully unlocked the image");
+    }
+    return Fence::NO_FENCE;
+}
+
 static void ImageReader_imageRelease(JNIEnv* env, jobject thiz, jobject image)
 {
     ALOGV("%s:", __FUNCTION__);
@@ -942,156 +463,18 @@
         return;
     }
 
-    if (ctx->isOpaque()) {
-        BufferItemConsumer* opaqueConsumer = ctx->getOpaqueConsumer();
-        BufferItem* opaqueBuffer = Image_getOpaqueBuffer(env, image);
-        opaqueConsumer->releaseBuffer(*opaqueBuffer); // Not using fence for now.
-        Image_setOpaqueBuffer(env, image, NULL);
-        ctx->returnOpaqueBuffer(opaqueBuffer);
-        ALOGV("%s: Opaque Image has been released", __FUNCTION__);
-    } else {
-        CpuConsumer* consumer = ctx->getCpuConsumer();
-        CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, image);
-        if (!buffer) {
-            // Release an already closed image is harmless.
-            return;
-        }
-        consumer->unlockBuffer(*buffer);
-        Image_setBuffer(env, image, NULL);
-        ctx->returnLockedBuffer(buffer);
-        ALOGV("%s: Image (format: 0x%x) has been released", __FUNCTION__, ctx->getBufferFormat());
-    }
-}
-
-static jint ImageReader_opaqueImageSetup(JNIEnv* env, JNIImageReaderContext* ctx, jobject image) {
-    ALOGV("%s:", __FUNCTION__);
-    if (ctx == NULL || !ctx->isOpaque()) {
-        jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
-        return -1;
+    BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
+    BufferItem* buffer = Image_getBufferItem(env, image);
+    if (buffer == nullptr) {
+        // Release an already closed image is harmless.
+        return;
     }
 
-    BufferItemConsumer* opaqueConsumer = ctx->getOpaqueConsumer();
-    BufferItem* buffer = ctx->getOpaqueBuffer();
-    if (buffer == NULL) {
-        ALOGW("Unable to acquire a buffer item, very likely client tried to acquire more than"
-            " maxImages buffers");
-        return ACQUIRE_MAX_IMAGES;
-    }
-
-    status_t res = opaqueConsumer->acquireBuffer(buffer, 0);
-    if (res != OK) {
-        ctx->returnOpaqueBuffer(buffer);
-        if (res == INVALID_OPERATION) {
-            // Max number of images were already acquired.
-            ALOGE("%s: Max number of buffers allowed are already acquired : %s (%d)",
-                    __FUNCTION__, strerror(-res), res);
-            return ACQUIRE_MAX_IMAGES;
-        } else {
-            ALOGE("%s: Acquire image failed with error: %s (%d)",
-                    __FUNCTION__, strerror(-res), res);
-            return ACQUIRE_NO_BUFFERS;
-        }
-    }
-
-    // Set SurfaceImage instance member variables
-    Image_setOpaqueBuffer(env, image, buffer);
-    env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
-            static_cast<jlong>(buffer->mTimestamp));
-
-    return ACQUIRE_SUCCESS;
-}
-
-static jint ImageReader_lockedImageSetup(JNIEnv* env, JNIImageReaderContext* ctx, jobject image) {
-    CpuConsumer* consumer = ctx->getCpuConsumer();
-    CpuConsumer::LockedBuffer* buffer = ctx->getLockedBuffer();
-    if (buffer == NULL) {
-        ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than"
-            " maxImages buffers");
-        return ACQUIRE_MAX_IMAGES;
-    }
-    status_t res = consumer->lockNextBuffer(buffer);
-    if (res != NO_ERROR) {
-        ctx->returnLockedBuffer(buffer);
-        if (res != BAD_VALUE /*no buffers*/) {
-            if (res == NOT_ENOUGH_DATA) {
-                return ACQUIRE_MAX_IMAGES;
-            } else {
-                ALOGE("%s Fail to lockNextBuffer with error: %d ",
-                      __FUNCTION__, res);
-                jniThrowExceptionFmt(env, "java/lang/AssertionError",
-                          "Unknown error (%d) when we tried to lock buffer.",
-                          res);
-            }
-        }
-        return ACQUIRE_NO_BUFFERS;
-    }
-
-    if (buffer->flexFormat == HAL_PIXEL_FORMAT_YCrCb_420_SP) {
-        jniThrowException(env, "java/lang/UnsupportedOperationException",
-                "NV21 format is not supported by ImageReader");
-        return -1;
-    }
-
-    // Check if the left-top corner of the crop rect is origin, we currently assume this point is
-    // zero, will revist this once this assumption turns out problematic.
-    Point lt = buffer->crop.leftTop();
-    if (lt.x != 0 || lt.y != 0) {
-        jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
-                "crop left top corner [%d, %d] need to be at origin", lt.x, lt.y);
-        return -1;
-    }
-
-    // Check if the producer buffer configurations match what ImageReader configured.
-    int outputWidth = Image_getBufferWidth(buffer);
-    int outputHeight = Image_getBufferHeight(buffer);
-
-    int imgReaderFmt = ctx->getBufferFormat();
-    int imageReaderWidth = ctx->getBufferWidth();
-    int imageReaderHeight = ctx->getBufferHeight();
-    if ((buffer->format != HAL_PIXEL_FORMAT_BLOB) && (imgReaderFmt != HAL_PIXEL_FORMAT_BLOB) &&
-            (imageReaderWidth != outputWidth || imageReaderHeight != outputHeight)) {
-        ALOGV("%s: Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
-                __FUNCTION__, outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
-    }
-
-    int bufFmt = buffer->format;
-    if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888) {
-        bufFmt = buffer->flexFormat;
-    }
-    if (imgReaderFmt != bufFmt) {
-        if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 && (bufFmt ==
-                HAL_PIXEL_FORMAT_YCrCb_420_SP || bufFmt == HAL_PIXEL_FORMAT_YV12)) {
-            // Special casing for when producer switches to a format compatible with flexible YUV
-            // (HAL_PIXEL_FORMAT_YCbCr_420_888).
-            ctx->setBufferFormat(bufFmt);
-            ALOGD("%s: Overriding buffer format YUV_420_888 to %x.", __FUNCTION__, bufFmt);
-        } else if (imgReaderFmt == HAL_PIXEL_FORMAT_BLOB && bufFmt == HAL_PIXEL_FORMAT_RGBA_8888) {
-            // Using HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers containing JPEGs to get around SW
-            // write limitations for (b/17379185).
-            ALOGD("%s: Receiving JPEG in HAL_PIXEL_FORMAT_RGBA_8888 buffer.", __FUNCTION__);
-        } else {
-            // Return the buffer to the queue.
-            consumer->unlockBuffer(*buffer);
-            ctx->returnLockedBuffer(buffer);
-
-            // Throw exception
-            ALOGE("Producer output buffer format: 0x%x, ImageReader configured format: 0x%x",
-                    buffer->format, ctx->getBufferFormat());
-            String8 msg;
-            msg.appendFormat("The producer output buffer format 0x%x doesn't "
-                    "match the ImageReader's configured buffer format 0x%x.",
-                    bufFmt, ctx->getBufferFormat());
-            jniThrowException(env, "java/lang/UnsupportedOperationException",
-                    msg.string());
-            return -1;
-        }
-    }
-    // Set SurfaceImage instance member variables
-    Image_setBuffer(env, image, buffer);
-    env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
-            static_cast<jlong>(buffer->timestamp));
-
-    return ACQUIRE_SUCCESS;
+    sp<Fence> releaseFence = Image_unlockIfLocked(env, image);
+    bufferConsumer->releaseBuffer(*buffer, releaseFence);
+    Image_setBufferItem(env, image, NULL);
+    ctx->returnBufferItem(buffer);
+    ALOGV("%s: Image (format: 0x%x) has been released", __FUNCTION__, ctx->getBufferFormat());
 }
 
 static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) {
@@ -1103,11 +486,99 @@
         return -1;
     }
 
-    if (ctx->isOpaque()) {
-        return ImageReader_opaqueImageSetup(env, ctx, image);
-    } else {
-        return ImageReader_lockedImageSetup(env, ctx, image);
+    BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
+    BufferItem* buffer = ctx->getBufferItem();
+    if (buffer == NULL) {
+        ALOGW("Unable to acquire a buffer item, very likely client tried to acquire more than"
+            " maxImages buffers");
+        return ACQUIRE_MAX_IMAGES;
     }
+
+    status_t res = bufferConsumer->acquireBuffer(buffer, 0);
+    if (res != OK) {
+        ctx->returnBufferItem(buffer);
+        if (res != BufferQueue::NO_BUFFER_AVAILABLE) {
+            if (res == INVALID_OPERATION) {
+                // Max number of images were already acquired.
+                ALOGE("%s: Max number of buffers allowed are already acquired : %s (%d)",
+                        __FUNCTION__, strerror(-res), res);
+                return ACQUIRE_MAX_IMAGES;
+            } else {
+                ALOGE("%s: Acquire image failed with some unknown error: %s (%d)",
+                        __FUNCTION__, strerror(-res), res);
+                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                        "Unknown error (%d) when we tried to acquire an image.",
+                                          res);
+                return ACQUIRE_NO_BUFFERS;
+            }
+        }
+        // This isn't really an error case, as the application may acquire buffer at any time.
+        return ACQUIRE_NO_BUFFERS;
+    }
+
+    // Add some extra checks for non-opaque formats.
+    if (!isFormatOpaque(ctx->getBufferFormat())) {
+        // Check if the left-top corner of the crop rect is origin, we currently assume this point is
+        // zero, will revisit this once this assumption turns out problematic.
+        Point lt = buffer->mCrop.leftTop();
+        if (lt.x != 0 || lt.y != 0) {
+            jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
+                    "crop left top corner [%d, %d] need to be at origin", lt.x, lt.y);
+            return -1;
+        }
+
+        // Check if the producer buffer configurations match what ImageReader configured.
+        int outputWidth = getBufferWidth(buffer);
+        int outputHeight = getBufferHeight(buffer);
+
+        int imgReaderFmt = ctx->getBufferFormat();
+        int imageReaderWidth = ctx->getBufferWidth();
+        int imageReaderHeight = ctx->getBufferHeight();
+        int bufferFormat = buffer->mGraphicBuffer->getPixelFormat();
+        if ((bufferFormat != HAL_PIXEL_FORMAT_BLOB) && (imgReaderFmt != HAL_PIXEL_FORMAT_BLOB) &&
+                (imageReaderWidth != outputWidth || imageReaderHeight != outputHeight)) {
+            ALOGV("%s: Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
+                    __FUNCTION__, outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
+        }
+        if (imgReaderFmt != bufferFormat) {
+            if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 &&
+                    isPossiblyYUV(bufferFormat)) {
+                // Treat formats that are compatible with flexible YUV
+                // (HAL_PIXEL_FORMAT_YCbCr_420_888) as HAL_PIXEL_FORMAT_YCbCr_420_888.
+                ALOGV("%s: Treat buffer format to 0x%x as HAL_PIXEL_FORMAT_YCbCr_420_888",
+                        __FUNCTION__, bufferFormat);
+            } else if (imgReaderFmt == HAL_PIXEL_FORMAT_BLOB &&
+                    bufferFormat == HAL_PIXEL_FORMAT_RGBA_8888) {
+                // Using HAL_PIXEL_FORMAT_RGBA_8888 Gralloc buffers containing JPEGs to get around
+                // SW write limitations for (b/17379185).
+                ALOGV("%s: Receiving JPEG in HAL_PIXEL_FORMAT_RGBA_8888 buffer.", __FUNCTION__);
+            } else {
+                // Return the buffer to the queue. No need to provide fence, as this buffer wasn't
+                // used anywhere yet.
+                bufferConsumer->releaseBuffer(*buffer);
+                ctx->returnBufferItem(buffer);
+
+                // Throw exception
+                ALOGE("Producer output buffer format: 0x%x, ImageReader configured format: 0x%x",
+                        bufferFormat, ctx->getBufferFormat());
+                String8 msg;
+                msg.appendFormat("The producer output buffer format 0x%x doesn't "
+                        "match the ImageReader's configured buffer format 0x%x.",
+                        bufferFormat, ctx->getBufferFormat());
+                jniThrowException(env, "java/lang/UnsupportedOperationException",
+                        msg.string());
+                return -1;
+            }
+        }
+
+    }
+
+    // Set SurfaceImage instance member variables
+    Image_setBufferItem(env, image, buffer);
+    env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
+            static_cast<jlong>(buffer->mTimestamp));
+
+    return ACQUIRE_SUCCESS;
 }
 
 static jint ImageReader_detachImage(JNIEnv* env, jobject thiz, jobject image) {
@@ -1118,29 +589,23 @@
         return -1;
     }
 
-    status_t res = OK;
-    if (!ctx->isOpaque()) {
-        // TODO: Non-Opaque format detach is not implemented yet.
-        jniThrowRuntimeException(env,
-                "nativeDetachImage is not implemented yet for non-opaque format !!!");
-        return -1;
-    }
-
-    BufferItemConsumer* opaqueConsumer = ctx->getOpaqueConsumer();
-    BufferItem* opaqueBuffer = Image_getOpaqueBuffer(env, image);
-    if (!opaqueBuffer) {
+    BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer();
+    BufferItem* buffer = Image_getBufferItem(env, image);
+    if (!buffer) {
         ALOGE(
-                "Opaque Image already released and can not be detached from ImageReader!!!");
+                "Image already released and can not be detached from ImageReader!!!");
         jniThrowException(env, "java/lang/IllegalStateException",
-                "Opaque Image detach from ImageReader failed: buffer was already released");
+                "Image detach from ImageReader failed: buffer was already released");
         return -1;
     }
 
-    res = opaqueConsumer->detachBuffer(opaqueBuffer->mSlot);
+    status_t res = OK;
+    Image_unlockIfLocked(env, image);
+    res = bufferConsumer->detachBuffer(buffer->mSlot);
     if (res != OK) {
-        ALOGE("Opaque Image detach failed: %s (%d)!!!", strerror(-res), res);
+        ALOGE("Image detach failed: %s (%d)!!!", strerror(-res), res);
         jniThrowRuntimeException(env,
-                "nativeDetachImage failed for opaque image!!!");
+                "nativeDetachImage failed for image!!!");
         return res;
     }
     return OK;
@@ -1152,7 +617,7 @@
 
     IGraphicBufferProducer* gbp = ImageReader_getProducer(env, thiz);
     if (gbp == NULL) {
-        jniThrowRuntimeException(env, "CpuConsumer is uninitialized");
+        jniThrowRuntimeException(env, "Buffer consumer is uninitialized");
         return NULL;
     }
 
@@ -1160,98 +625,115 @@
     return android_view_Surface_createFromIGraphicBufferProducer(env, gbp);
 }
 
-static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx, int readerFormat)
+static void Image_getLockedImage(JNIEnv* env, jobject thiz, LockedImage *image) {
+    ALOGV("%s", __FUNCTION__);
+    BufferItem* buffer = Image_getBufferItem(env, thiz);
+    if (buffer == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "Image is not initialized");
+        return;
+    }
+
+    status_t res = lockImageFromBuffer(buffer,
+            GRALLOC_USAGE_SW_READ_OFTEN, buffer->mFence->dup(), image);
+    if (res != OK) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "lock buffer failed for format 0x%x",
+                buffer->mGraphicBuffer->getPixelFormat());
+        return;
+    }
+
+    // Carry over some fields from BufferItem.
+    image->crop        = buffer->mCrop;
+    image->transform   = buffer->mTransform;
+    image->scalingMode = buffer->mScalingMode;
+    image->timestamp   = buffer->mTimestamp;
+    image->dataSpace   = buffer->mDataSpace;
+    image->frameNumber = buffer->mFrameNumber;
+
+    ALOGV("%s: Successfully locked the image", __FUNCTION__);
+    // crop, transform, scalingMode, timestamp, and frameNumber should be set by producer,
+    // and we don't set them here.
+}
+
+static void Image_getLockedImageInfo(JNIEnv* env, LockedImage* buffer, int idx,
+        int32_t writerFormat, uint8_t **base, uint32_t *size, int *pixelStride, int *rowStride) {
+    ALOGV("%s", __FUNCTION__);
+
+    status_t res = getLockedImageInfo(buffer, idx, writerFormat, base, size,
+            pixelStride, rowStride);
+    if (res != OK) {
+        jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
+                             "Pixel format: 0x%x is unsupported", buffer->flexFormat);
+    }
+}
+
+static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
+        int numPlanes, int readerFormat)
 {
-    int rowStride, pixelStride;
+    ALOGV("%s: create SurfacePlane array with size %d", __FUNCTION__, numPlanes);
+    int rowStride = 0;
+    int pixelStride = 0;
+    uint8_t *pData = NULL;
+    uint32_t dataSize = 0;
+    jobject byteBuffer = NULL;
+
     PublicFormat publicReaderFormat = static_cast<PublicFormat>(readerFormat);
     int halReaderFormat = android_view_Surface_mapPublicFormatToHalFormat(
         publicReaderFormat);
 
-    ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
+    if (isFormatOpaque(halReaderFormat) && numPlanes > 0) {
+        String8 msg;
+        msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
+                " must be 0", halReaderFormat, numPlanes);
+        jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+        return NULL;
+    }
+
+    jobjectArray surfacePlanes = env->NewObjectArray(numPlanes, gSurfacePlaneClassInfo.clazz,
+            /*initial_element*/NULL);
+    if (surfacePlanes == NULL) {
+        jniThrowRuntimeException(env, "Failed to create SurfacePlane arrays,"
+                " probably out of memory");
+        return NULL;
+    }
     if (isFormatOpaque(halReaderFormat)) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                "Opaque images from Opaque ImageReader do not have any planes");
-        return NULL;
+        // Return 0 element surface array.
+        return surfacePlanes;
     }
 
-    CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz);
+    LockedImage lockedImg = LockedImage();
+    Image_getLockedImage(env, thiz, &lockedImg);
+    // Create all SurfacePlanes
+    for (int i = 0; i < numPlanes; i++) {
+        Image_getLockedImageInfo(env, &lockedImg, i, halReaderFormat,
+                &pData, &dataSize, &pixelStride, &rowStride);
+        byteBuffer = env->NewDirectByteBuffer(pData, dataSize);
+        if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
+            jniThrowException(env, "java/lang/IllegalStateException",
+                    "Failed to allocate ByteBuffer");
+            return NULL;
+        }
 
-    ALOG_ASSERT(buffer != NULL);
-    if (buffer == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
+        // Finally, create this SurfacePlane.
+        jobject surfacePlane = env->NewObject(gSurfacePlaneClassInfo.clazz,
+                    gSurfacePlaneClassInfo.ctor, thiz, rowStride, pixelStride, byteBuffer);
+        env->SetObjectArrayElement(surfacePlanes, i, surfacePlane);
     }
 
-    rowStride = Image_imageGetRowStride(env, buffer, idx, halReaderFormat);
-    pixelStride = Image_imageGetPixelStride(env, buffer, idx, halReaderFormat);
-
-    jobject surfPlaneObj = env->NewObject(gSurfacePlaneClassInfo.clazz,
-            gSurfacePlaneClassInfo.ctor, thiz, idx, rowStride, pixelStride);
-
-    return surfPlaneObj;
+    return surfacePlanes;
 }
 
-static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx, int readerFormat)
+static jint Image_getWidth(JNIEnv* env, jobject thiz)
 {
-    uint8_t *base = NULL;
-    uint32_t size = 0;
-    jobject byteBuffer;
-    PublicFormat readerPublicFormat = static_cast<PublicFormat>(readerFormat);
-    int readerHalFormat = android_view_Surface_mapPublicFormatToHalFormat(
-            readerPublicFormat);
-
-    ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
-
-    if (isFormatOpaque(readerHalFormat)) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                "Opaque images from Opaque ImageReader do not have any plane");
-        return NULL;
-    }
-
-    CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz);
-
-    if (buffer == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
-    }
-
-    // Create byteBuffer from native buffer
-    Image_getLockedBufferInfo(env, buffer, idx, &base, &size, readerHalFormat);
-
-    if (size > static_cast<uint32_t>(INT32_MAX)) {
-        // Byte buffer have 'int capacity', so check the range
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                "Size too large for bytebuffer capacity %" PRIu32, size);
-        return NULL;
-    }
-
-    byteBuffer = env->NewDirectByteBuffer(base, size);
-    // TODO: throw dvm exOutOfMemoryError?
-    if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
-        jniThrowException(env, "java/lang/IllegalStateException", "Failed to allocate ByteBuffer");
-    }
-
-    return byteBuffer;
+    BufferItem* buffer = Image_getBufferItem(env, thiz);
+    return getBufferWidth(buffer);
 }
 
-static jint Image_getWidth(JNIEnv* env, jobject thiz, jint format)
+static jint Image_getHeight(JNIEnv* env, jobject thiz)
 {
-    if (isFormatOpaque(format)) {
-        BufferItem* opaqueBuffer = Image_getOpaqueBuffer(env, thiz);
-        return Image_getOpaqueBufferWidth(opaqueBuffer);
-    } else {
-        CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz);
-        return Image_getBufferWidth(buffer);
-    }
-}
-
-static jint Image_getHeight(JNIEnv* env, jobject thiz, jint format)
-{
-    if (isFormatOpaque(format)) {
-        BufferItem* opaqueBuffer = Image_getOpaqueBuffer(env, thiz);
-        return Image_getOpaqueBufferHeight(opaqueBuffer);
-    } else {
-        CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz);
-        return Image_getBufferHeight(buffer);
-    }
+    BufferItem* buffer = Image_getBufferItem(env, thiz);
+    return getBufferHeight(buffer);
 }
 
 static jint Image_getFormat(JNIEnv* env, jobject thiz, jint readerFormat)
@@ -1260,20 +742,21 @@
         // Assuming opaque reader produce opaque images.
         return static_cast<jint>(PublicFormat::PRIVATE);
     } else {
-        CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz);
+        BufferItem* buffer = Image_getBufferItem(env, thiz);
         int readerHalFormat = android_view_Surface_mapPublicFormatToHalFormat(
                 static_cast<PublicFormat>(readerFormat));
-        int32_t fmt = applyFormatOverrides(buffer->flexFormat, readerHalFormat);
+        int32_t fmt = applyFormatOverrides(
+                buffer->mGraphicBuffer->getPixelFormat(), readerHalFormat);
         // Override the image format to HAL_PIXEL_FORMAT_YCbCr_420_888 if the actual format is
         // NV21 or YV12. This could only happen when the Gralloc HAL version is v0.1 thus doesn't
         // support lockycbcr(), the CpuConsumer need to use the lock() method in the
         // lockNextBuffer() call. For Gralloc HAL v0.2 or newer, this format should already be
         // overridden to HAL_PIXEL_FORMAT_YCbCr_420_888 for the flexible YUV compatible formats.
-        if (fmt == HAL_PIXEL_FORMAT_YCrCb_420_SP || fmt == HAL_PIXEL_FORMAT_YV12) {
+        if (isPossiblyYUV(fmt)) {
             fmt = HAL_PIXEL_FORMAT_YCbCr_420_888;
         }
         PublicFormat publicFmt = android_view_Surface_mapHalFormatDataspaceToPublicFormat(
-                fmt, buffer->dataSpace);
+                fmt, buffer->mDataSpace);
         return static_cast<jint>(publicFmt);
     }
 }
@@ -1293,11 +776,10 @@
 };
 
 static const JNINativeMethod gImageMethods[] = {
-    {"nativeImageGetBuffer",   "(II)Ljava/nio/ByteBuffer;",   (void*)Image_getByteBuffer },
-    {"nativeCreatePlane",      "(II)Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
-                                                              (void*)Image_createSurfacePlane },
-    {"nativeGetWidth",         "(I)I",                        (void*)Image_getWidth },
-    {"nativeGetHeight",        "(I)I",                        (void*)Image_getHeight },
+    {"nativeCreatePlanes",      "(II)[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
+                                                              (void*)Image_createSurfacePlanes },
+    {"nativeGetWidth",         "()I",                        (void*)Image_getWidth },
+    {"nativeGetHeight",        "()I",                        (void*)Image_getHeight },
     {"nativeGetFormat",        "(I)I",                        (void*)Image_getFormat },
 };
 
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index f50da85..d5d9fc9 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -16,34 +16,28 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ImageWriter_JNI"
+#include "android_media_Utils.h"
+
 #include <utils/Log.h>
 #include <utils/String8.h>
 
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
-#include <gui/CpuConsumer.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
 #include <camera3.h>
-
 #include <jni.h>
 #include <JNIHelp.h>
 
 #include <stdint.h>
 #include <inttypes.h>
 
-#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
-
 #define IMAGE_BUFFER_JNI_ID           "mNativeBuffer"
 
 // ----------------------------------------------------------------------------
 
 using namespace android;
 
-enum {
-    IMAGE_WRITER_MAX_NUM_PLANES = 3,
-};
-
 static struct {
     jmethodID postEventFromNative;
     jfieldID mWriterFormat;
@@ -60,8 +54,6 @@
     jmethodID ctor;
 } gSurfacePlaneClassInfo;
 
-typedef CpuConsumer::LockedBuffer LockedImage;
-
 // ----------------------------------------------------------------------------
 
 class JNIImageWriterContext : public BnProducerListener {
@@ -181,13 +173,11 @@
 
 // -------------------------------Private method declarations--------------
 
-static bool isPossiblyYUV(PixelFormat format);
 static void Image_setNativeContext(JNIEnv* env, jobject thiz,
         sp<GraphicBuffer> buffer, int fenceFd);
 static void Image_getNativeContext(JNIEnv* env, jobject thiz,
         GraphicBuffer** buffer, int* fenceFd);
 static void Image_unlockIfLocked(JNIEnv* env, jobject thiz);
-static bool isFormatOpaque(int format);
 
 // --------------------------ImageWriter methods---------------------------------------
 
@@ -672,28 +662,6 @@
     return buffer->getHeight();
 }
 
-// Some formats like JPEG defined with different values between android.graphics.ImageFormat and
-// graphics.h, need convert to the one defined in graphics.h here.
-static int Image_getPixelFormat(JNIEnv* env, int format) {
-    int jpegFormat;
-    jfieldID fid;
-
-    ALOGV("%s: format = 0x%x", __FUNCTION__, format);
-
-    jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
-    ALOG_ASSERT(imageFormatClazz != NULL);
-
-    fid = env->GetStaticFieldID(imageFormatClazz, "JPEG", "I");
-    jpegFormat = env->GetStaticIntField(imageFormatClazz, fid);
-
-    // Translate the JPEG to BLOB for camera purpose.
-    if (format == jpegFormat) {
-        format = HAL_PIXEL_FORMAT_BLOB;
-    }
-
-    return format;
-}
-
 static jint Image_getFormat(JNIEnv* env, jobject thiz) {
     ALOGV("%s", __FUNCTION__);
     GraphicBuffer* buffer;
@@ -704,7 +672,10 @@
         return 0;
     }
 
-    return Image_getPixelFormat(env, buffer->getPixelFormat());
+    // ImageWriter doesn't support data space yet, assuming it is unknown.
+    PublicFormat publicFmt = android_view_Surface_mapHalFormatDataspaceToPublicFormat(
+            buffer->getPixelFormat(), HAL_DATASPACE_UNKNOWN);
+    return static_cast<jint>(publicFmt);
 }
 
 static void Image_setFenceFd(JNIEnv* env, jobject thiz, int fenceFd) {
@@ -723,272 +694,34 @@
         return;
     }
 
-    void* pData = NULL;
-    android_ycbcr ycbcr = android_ycbcr();
-    status_t res;
-    int format = Image_getFormat(env, thiz);
-    int flexFormat = format;
-    if (isPossiblyYUV(format)) {
-        // ImageWriter doesn't use crop by itself, app sets it, use the no crop version.
-        res = buffer->lockAsyncYCbCr(GRALLOC_USAGE_SW_WRITE_OFTEN, &ycbcr, fenceFd);
-        // Clear the fenceFd as it is already consumed by lock call.
-        Image_setFenceFd(env, thiz, /*fenceFd*/-1);
-        if (res != OK) {
-            jniThrowRuntimeException(env, "lockAsyncYCbCr failed for YUV buffer");
-            return;
-        }
-        pData = ycbcr.y;
-        flexFormat = HAL_PIXEL_FORMAT_YCbCr_420_888;
+    // ImageWriter doesn't use crop by itself, app sets it, use the no crop version.
+    const Rect noCrop(buffer->width, buffer->height);
+    status_t res = lockImageFromBuffer(
+            buffer, GRALLOC_USAGE_SW_WRITE_OFTEN, noCrop, fenceFd, image);
+    // Clear the fenceFd as it is already consumed by lock call.
+    Image_setFenceFd(env, thiz, /*fenceFd*/-1);
+    if (res != OK) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "lock buffer failed for format 0x%x",
+                buffer->getPixelFormat());
+        return;
     }
 
-    // lockAsyncYCbCr for YUV is unsuccessful.
-    if (pData == NULL) {
-        res = buffer->lockAsync(GRALLOC_USAGE_SW_WRITE_OFTEN, &pData, fenceFd);
-        if (res != OK) {
-            jniThrowRuntimeException(env, "lockAsync failed");
-            return;
-        }
-    }
-
-    image->data = reinterpret_cast<uint8_t*>(pData);
-    image->width = buffer->getWidth();
-    image->height = buffer->getHeight();
-    image->format = format;
-    image->flexFormat = flexFormat;
-    image->stride = (ycbcr.y != NULL) ? static_cast<uint32_t>(ycbcr.ystride) : buffer->getStride();
-
-    image->dataCb = reinterpret_cast<uint8_t*>(ycbcr.cb);
-    image->dataCr = reinterpret_cast<uint8_t*>(ycbcr.cr);
-    image->chromaStride = static_cast<uint32_t>(ycbcr.cstride);
-    image->chromaStep = static_cast<uint32_t>(ycbcr.chroma_step);
-    ALOGV("Successfully locked the image");
+    ALOGV("%s: Successfully locked the image", __FUNCTION__);
     // crop, transform, scalingMode, timestamp, and frameNumber should be set by producer,
     // and we don't set them here.
 }
 
-static bool usingRGBAToJpegOverride(int32_t bufferFormat, int32_t writerCtxFormat) {
-    return writerCtxFormat == HAL_PIXEL_FORMAT_BLOB && bufferFormat == HAL_PIXEL_FORMAT_RGBA_8888;
-}
-
-static int32_t applyFormatOverrides(int32_t bufferFormat, int32_t writerCtxFormat)
-{
-    // Using HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers containing JPEGs to get around SW
-    // write limitations for some platforms (b/17379185).
-    if (usingRGBAToJpegOverride(bufferFormat, writerCtxFormat)) {
-        return HAL_PIXEL_FORMAT_BLOB;
-    }
-    return bufferFormat;
-}
-
-static uint32_t Image_getJpegSize(LockedImage* buffer, bool usingRGBAOverride) {
-    ALOGV("%s", __FUNCTION__);
-    ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
-    uint32_t size = 0;
-    uint32_t width = buffer->width;
-    uint8_t* jpegBuffer = buffer->data;
-
-    if (usingRGBAOverride) {
-        width = (buffer->width + buffer->stride * (buffer->height - 1)) * 4;
-    }
-
-    // First check for JPEG transport header at the end of the buffer
-    uint8_t* header = jpegBuffer + (width - sizeof(struct camera3_jpeg_blob));
-    struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header);
-    if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID) {
-        size = blob->jpeg_size;
-        ALOGV("%s: Jpeg size = %d", __FUNCTION__, size);
-    }
-
-    // failed to find size, default to whole buffer
-    if (size == 0) {
-        /*
-         * This is a problem because not including the JPEG header
-         * means that in certain rare situations a regular JPEG blob
-         * will be misidentified as having a header, in which case
-         * we will get a garbage size value.
-         */
-        ALOGW("%s: No JPEG header detected, defaulting to size=width=%d",
-                __FUNCTION__, width);
-        size = width;
-    }
-
-    return size;
-}
-
 static void Image_getLockedImageInfo(JNIEnv* env, LockedImage* buffer, int idx,
         int32_t writerFormat, uint8_t **base, uint32_t *size, int *pixelStride, int *rowStride) {
     ALOGV("%s", __FUNCTION__);
-    ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
-    ALOG_ASSERT(base != NULL, "base is NULL!!!");
-    ALOG_ASSERT(size != NULL, "size is NULL!!!");
-    ALOG_ASSERT(pixelStride != NULL, "pixelStride is NULL!!!");
-    ALOG_ASSERT(rowStride != NULL, "rowStride is NULL!!!");
-    ALOG_ASSERT((idx < IMAGE_WRITER_MAX_NUM_PLANES) && (idx >= 0));
 
-    ALOGV("%s: buffer: %p", __FUNCTION__, buffer);
-
-    uint32_t dataSize, ySize, cSize, cStride;
-    uint32_t pStride = 0, rStride = 0;
-    uint8_t *cb, *cr;
-    uint8_t *pData = NULL;
-    int bytesPerPixel = 0;
-
-    dataSize = ySize = cSize = cStride = 0;
-    int32_t fmt = buffer->flexFormat;
-
-    bool usingRGBAOverride = usingRGBAToJpegOverride(fmt, writerFormat);
-    fmt = applyFormatOverrides(fmt, writerFormat);
-    switch (fmt) {
-        case HAL_PIXEL_FORMAT_YCbCr_420_888:
-            pData =
-                (idx == 0) ?
-                    buffer->data :
-                (idx == 1) ?
-                    buffer->dataCb :
-                buffer->dataCr;
-            // only map until last pixel
-            if (idx == 0) {
-                pStride = 1;
-                rStride = buffer->stride;
-                dataSize = buffer->stride * (buffer->height - 1) + buffer->width;
-            } else {
-                pStride = buffer->chromaStep;
-                rStride = buffer->chromaStride;
-                dataSize = buffer->chromaStride * (buffer->height / 2 - 1) +
-                        buffer->chromaStep * (buffer->width / 2 - 1) + 1;
-            }
-            break;
-        // NV21
-        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
-            cr = buffer->data + (buffer->stride * buffer->height);
-            cb = cr + 1;
-            // only map until last pixel
-            ySize = buffer->width * (buffer->height - 1) + buffer->width;
-            cSize = buffer->width * (buffer->height / 2 - 1) + buffer->width - 1;
-
-            pData =
-                (idx == 0) ?
-                    buffer->data :
-                (idx == 1) ?
-                    cb:
-                cr;
-
-            dataSize = (idx == 0) ? ySize : cSize;
-            pStride = (idx == 0) ? 1 : 2;
-            rStride = buffer->width;
-            break;
-        case HAL_PIXEL_FORMAT_YV12:
-            // Y and C stride need to be 16 pixel aligned.
-            LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
-                                "Stride is not 16 pixel aligned %d", buffer->stride);
-
-            ySize = buffer->stride * buffer->height;
-            cStride = ALIGN(buffer->stride / 2, 16);
-            cr = buffer->data + ySize;
-            cSize = cStride * buffer->height / 2;
-            cb = cr + cSize;
-
-            pData =
-                (idx == 0) ?
-                    buffer->data :
-                (idx == 1) ?
-                    cb :
-                cr;
-            dataSize = (idx == 0) ? ySize : cSize;
-            pStride = 1;
-            rStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
-            break;
-        case HAL_PIXEL_FORMAT_Y8:
-            // Single plane, 8bpp.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height;
-            pStride = 1;
-            rStride = buffer->stride;
-            break;
-        case HAL_PIXEL_FORMAT_Y16:
-            bytesPerPixel = 2;
-            // Single plane, 16bpp, strides are specified in pixels, not in bytes
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            pStride = bytesPerPixel;
-            rStride = buffer->stride * 2;
-            break;
-        case HAL_PIXEL_FORMAT_BLOB:
-            // Used for JPEG data, height must be 1, width == size, single plane.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            ALOG_ASSERT(buffer->height == 1, "JPEG should has height value %d", buffer->height);
-
-            pData = buffer->data;
-            dataSize = Image_getJpegSize(buffer, usingRGBAOverride);
-            pStride = bytesPerPixel;
-            rowStride = 0;
-            break;
-        case HAL_PIXEL_FORMAT_RAW16:
-            // Single plane 16bpp bayer data.
-            bytesPerPixel = 2;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            pStride = bytesPerPixel;
-            rStride = buffer->stride * 2;
-            break;
-        case HAL_PIXEL_FORMAT_RAW10:
-            // Single plane 10bpp bayer data.
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            LOG_ALWAYS_FATAL_IF(buffer->width % 4,
-                                "Width is not multiple of 4 %d", buffer->width);
-            LOG_ALWAYS_FATAL_IF(buffer->height % 2,
-                                "Height is not even %d", buffer->height);
-            LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 10 / 8),
-                                "stride (%d) should be at least %d",
-                                buffer->stride, buffer->width * 10 / 8);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height;
-            pStride = 0;
-            rStride = buffer->stride;
-            break;
-        case HAL_PIXEL_FORMAT_RGBA_8888:
-        case HAL_PIXEL_FORMAT_RGBX_8888:
-            // Single plane, 32bpp.
-            bytesPerPixel = 4;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            pStride = bytesPerPixel;
-            rStride = buffer->stride * 4;
-            break;
-        case HAL_PIXEL_FORMAT_RGB_565:
-            // Single plane, 16bpp.
-            bytesPerPixel = 2;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            pStride = bytesPerPixel;
-            rStride = buffer->stride * 2;
-            break;
-        case HAL_PIXEL_FORMAT_RGB_888:
-            // Single plane, 24bpp.
-            bytesPerPixel = 3;
-            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
-            pData = buffer->data;
-            dataSize = buffer->stride * buffer->height * bytesPerPixel;
-            pStride = bytesPerPixel;
-            rStride = buffer->stride * 3;
-            break;
-        default:
-            jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
-                                 "Pixel format: 0x%x is unsupported", fmt);
-            break;
+    status_t res = getLockedImageInfo(buffer, idx, writerFormat, base, size,
+            pixelStride, rowStride);
+    if (res != OK) {
+        jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException",
+                             "Pixel format: 0x%x is unsupported", buffer->flexFormat);
     }
-
-    *base = pData;
-    *size = dataSize;
-    *pixelStride = pStride;
-    *rowStride = rStride;
 }
 
 static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
@@ -1024,7 +757,8 @@
     Image_getLockedImage(env, thiz, &lockedImg);
 
     // Create all SurfacePlanes
-    writerFormat = Image_getPixelFormat(env, writerFormat);
+    PublicFormat publicWriterFormat = static_cast<PublicFormat>(writerFormat);
+    writerFormat = android_view_Surface_mapPublicFormatToHalFormat(publicWriterFormat);
     for (int i = 0; i < numPlanes; i++) {
         Image_getLockedImageInfo(env, &lockedImg, i, writerFormat,
                 &pData, &dataSize, &pixelStride, &rowStride);
@@ -1044,39 +778,6 @@
     return surfacePlanes;
 }
 
-// -------------------------------Private convenience methods--------------------
-
-static bool isFormatOpaque(int format) {
-    // Only treat IMPLEMENTATION_DEFINED as an opaque format for now.
-    return format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
-}
-
-static bool isPossiblyYUV(PixelFormat format) {
-    switch (static_cast<int>(format)) {
-        case HAL_PIXEL_FORMAT_RGBA_8888:
-        case HAL_PIXEL_FORMAT_RGBX_8888:
-        case HAL_PIXEL_FORMAT_RGB_888:
-        case HAL_PIXEL_FORMAT_RGB_565:
-        case HAL_PIXEL_FORMAT_BGRA_8888:
-        case HAL_PIXEL_FORMAT_Y8:
-        case HAL_PIXEL_FORMAT_Y16:
-        case HAL_PIXEL_FORMAT_RAW16:
-        case HAL_PIXEL_FORMAT_RAW10:
-        case HAL_PIXEL_FORMAT_RAW_OPAQUE:
-        case HAL_PIXEL_FORMAT_BLOB:
-        case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
-            return false;
-
-        case HAL_PIXEL_FORMAT_YV12:
-        case HAL_PIXEL_FORMAT_YCbCr_420_888:
-        case HAL_PIXEL_FORMAT_YCbCr_422_SP:
-        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
-        case HAL_PIXEL_FORMAT_YCbCr_422_I:
-        default:
-            return true;
-    }
-}
-
 } // extern "C"
 
 // ----------------------------------------------------------------------------
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 9c4f7c4..f429661 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -26,6 +26,8 @@
 
 #include <nativehelper/ScopedLocalRef.h>
 
+#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
+
 namespace android {
 
 FileStream::FileStream(const int fd)
@@ -509,5 +511,391 @@
     return OK;
 }
 
+// -----------Utility functions used by ImageReader/Writer JNI-----------------
+
+enum {
+    IMAGE_MAX_NUM_PLANES = 3,
+};
+
+bool usingRGBAToJpegOverride(int32_t imageFormat,
+        int32_t containerFormat) {
+    return containerFormat == HAL_PIXEL_FORMAT_BLOB && imageFormat == HAL_PIXEL_FORMAT_RGBA_8888;
+}
+
+int32_t applyFormatOverrides(int32_t imageFormat, int32_t containerFormat) {
+    // Using HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers containing JPEGs to get around SW
+    // write limitations for some platforms (b/17379185).
+    if (usingRGBAToJpegOverride(imageFormat, containerFormat)) {
+        return HAL_PIXEL_FORMAT_BLOB;
+    }
+    return containerFormat;
+}
+
+bool isFormatOpaque(int format) {
+    // This is the only opaque format exposed in the ImageFormat public API.
+    // Note that we do support CPU access for HAL_PIXEL_FORMAT_RAW_OPAQUE
+    // (ImageFormat#RAW_PRIVATE) so it doesn't count as opaque here.
+    return format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
+}
+
+bool isPossiblyYUV(PixelFormat format) {
+    switch (static_cast<int>(format)) {
+        case HAL_PIXEL_FORMAT_RGBA_8888:
+        case HAL_PIXEL_FORMAT_RGBX_8888:
+        case HAL_PIXEL_FORMAT_RGB_888:
+        case HAL_PIXEL_FORMAT_RGB_565:
+        case HAL_PIXEL_FORMAT_BGRA_8888:
+        case HAL_PIXEL_FORMAT_Y8:
+        case HAL_PIXEL_FORMAT_Y16:
+        case HAL_PIXEL_FORMAT_RAW16:
+        case HAL_PIXEL_FORMAT_RAW10:
+        case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+        case HAL_PIXEL_FORMAT_BLOB:
+        case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+            return false;
+
+        case HAL_PIXEL_FORMAT_YV12:
+        case HAL_PIXEL_FORMAT_YCbCr_420_888:
+        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+        default:
+            return true;
+    }
+}
+
+uint32_t Image_getJpegSize(LockedImage* buffer, bool usingRGBAOverride) {
+    ALOGV("%s", __FUNCTION__);
+    LOG_ALWAYS_FATAL_IF(buffer == NULL, "Input buffer is NULL!!!");
+    uint32_t size = 0;
+    uint32_t width = buffer->width;
+    uint8_t* jpegBuffer = buffer->data;
+
+    if (usingRGBAOverride) {
+        width = (buffer->width + buffer->stride * (buffer->height - 1)) * 4;
+    }
+
+    // First check for JPEG transport header at the end of the buffer
+    uint8_t* header = jpegBuffer + (width - sizeof(struct camera3_jpeg_blob));
+    struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header);
+    if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID) {
+        size = blob->jpeg_size;
+        ALOGV("%s: Jpeg size = %d", __FUNCTION__, size);
+    }
+
+    // failed to find size, default to whole buffer
+    if (size == 0) {
+        /*
+         * This is a problem because not including the JPEG header
+         * means that in certain rare situations a regular JPEG blob
+         * will be mis-identified as having a header, in which case
+         * we will get a garbage size value.
+         */
+        ALOGW("%s: No JPEG header detected, defaulting to size=width=%d",
+                __FUNCTION__, width);
+        size = width;
+    }
+
+    return size;
+}
+
+status_t getLockedImageInfo(LockedImage* buffer, int idx,
+        int32_t containerFormat, uint8_t **base, uint32_t *size, int *pixelStride, int *rowStride) {
+    ALOGV("%s", __FUNCTION__);
+    LOG_ALWAYS_FATAL_IF(buffer == NULL, "Input buffer is NULL!!!");
+    LOG_ALWAYS_FATAL_IF(base == NULL, "base is NULL!!!");
+    LOG_ALWAYS_FATAL_IF(size == NULL, "size is NULL!!!");
+    LOG_ALWAYS_FATAL_IF(pixelStride == NULL, "pixelStride is NULL!!!");
+    LOG_ALWAYS_FATAL_IF(rowStride == NULL, "rowStride is NULL!!!");
+    LOG_ALWAYS_FATAL_IF((idx >= IMAGE_MAX_NUM_PLANES) || (idx < 0), "idx (%d) is illegal", idx);
+
+    ALOGV("%s: buffer: %p", __FUNCTION__, buffer);
+
+    uint32_t dataSize, ySize, cSize, cStride;
+    uint32_t pStride = 0, rStride = 0;
+    uint8_t *cb, *cr;
+    uint8_t *pData = NULL;
+    int bytesPerPixel = 0;
+
+    dataSize = ySize = cSize = cStride = 0;
+    int32_t fmt = buffer->flexFormat;
+
+    bool usingRGBAOverride = usingRGBAToJpegOverride(fmt, containerFormat);
+    fmt = applyFormatOverrides(fmt, containerFormat);
+    switch (fmt) {
+        case HAL_PIXEL_FORMAT_YCbCr_420_888:
+            pData =
+                (idx == 0) ?
+                    buffer->data :
+                (idx == 1) ?
+                    buffer->dataCb :
+                buffer->dataCr;
+            // only map until last pixel
+            if (idx == 0) {
+                pStride = 1;
+                rStride = buffer->stride;
+                dataSize = buffer->stride * (buffer->height - 1) + buffer->width;
+            } else {
+                pStride = buffer->chromaStep;
+                rStride = buffer->chromaStride;
+                dataSize = buffer->chromaStride * (buffer->height / 2 - 1) +
+                        buffer->chromaStep * (buffer->width / 2 - 1) + 1;
+            }
+            break;
+        // NV21
+        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+            cr = buffer->data + (buffer->stride * buffer->height);
+            cb = cr + 1;
+            // only map until last pixel
+            ySize = buffer->width * (buffer->height - 1) + buffer->width;
+            cSize = buffer->width * (buffer->height / 2 - 1) + buffer->width - 1;
+
+            pData =
+                (idx == 0) ?
+                    buffer->data :
+                (idx == 1) ?
+                    cb:
+                cr;
+
+            dataSize = (idx == 0) ? ySize : cSize;
+            pStride = (idx == 0) ? 1 : 2;
+            rStride = buffer->width;
+            break;
+        case HAL_PIXEL_FORMAT_YV12:
+            // Y and C stride need to be 16 pixel aligned.
+            LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
+                                "Stride is not 16 pixel aligned %d", buffer->stride);
+
+            ySize = buffer->stride * buffer->height;
+            cStride = ALIGN(buffer->stride / 2, 16);
+            cr = buffer->data + ySize;
+            cSize = cStride * buffer->height / 2;
+            cb = cr + cSize;
+
+            pData =
+                (idx == 0) ?
+                    buffer->data :
+                (idx == 1) ?
+                    cb :
+                cr;
+            dataSize = (idx == 0) ? ySize : cSize;
+            pStride = 1;
+            rStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
+            break;
+        case HAL_PIXEL_FORMAT_Y8:
+            // Single plane, 8bpp.
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height;
+            pStride = 1;
+            rStride = buffer->stride;
+            break;
+        case HAL_PIXEL_FORMAT_Y16:
+            bytesPerPixel = 2;
+            // Single plane, 16bpp, strides are specified in pixels, not in bytes
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height * bytesPerPixel;
+            pStride = bytesPerPixel;
+            rStride = buffer->stride * 2;
+            break;
+        case HAL_PIXEL_FORMAT_BLOB:
+            // Used for JPEG data, height must be 1, width == size, single plane.
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            LOG_ALWAYS_FATAL_IF(buffer->height != 1,
+                    "BLOB format buffer should has height value %d", buffer->height);
+
+            pData = buffer->data;
+            dataSize = Image_getJpegSize(buffer, usingRGBAOverride);
+            pStride = 0;
+            rStride = 0;
+            break;
+        case HAL_PIXEL_FORMAT_RAW16:
+            // Single plane 16bpp bayer data.
+            bytesPerPixel = 2;
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height * bytesPerPixel;
+            pStride = bytesPerPixel;
+            rStride = buffer->stride * 2;
+            break;
+        case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+            // Used for RAW_OPAQUE data, height must be 1, width == size, single plane.
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            LOG_ALWAYS_FATAL_IF(buffer->height != 1,
+                    "RAW_PRIVATE should has height value one but got %d", buffer->height);
+            pData = buffer->data;
+            dataSize = buffer->width;
+            pStride = 0; // RAW OPAQUE doesn't have pixel stride
+            rStride = 0; // RAW OPAQUE doesn't have row stride
+            break;
+        case HAL_PIXEL_FORMAT_RAW10:
+            // Single plane 10bpp bayer data.
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            LOG_ALWAYS_FATAL_IF(buffer->width % 4,
+                                "Width is not multiple of 4 %d", buffer->width);
+            LOG_ALWAYS_FATAL_IF(buffer->height % 2,
+                                "Height is not even %d", buffer->height);
+            LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 10 / 8),
+                                "stride (%d) should be at least %d",
+                                buffer->stride, buffer->width * 10 / 8);
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height;
+            pStride = 0;
+            rStride = buffer->stride;
+            break;
+        case HAL_PIXEL_FORMAT_RAW12:
+            // Single plane 10bpp bayer data.
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            LOG_ALWAYS_FATAL_IF(buffer->width % 4,
+                                "Width is not multiple of 4 %d", buffer->width);
+            LOG_ALWAYS_FATAL_IF(buffer->height % 2,
+                                "Height is not even %d", buffer->height);
+            LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 12 / 8),
+                                "stride (%d) should be at least %d",
+                                buffer->stride, buffer->width * 12 / 8);
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height;
+            pStride = 0;
+            rStride = buffer->stride;
+            break;
+        case HAL_PIXEL_FORMAT_RGBA_8888:
+        case HAL_PIXEL_FORMAT_RGBX_8888:
+            // Single plane, 32bpp.
+            bytesPerPixel = 4;
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height * bytesPerPixel;
+            pStride = bytesPerPixel;
+            rStride = buffer->stride * 4;
+            break;
+        case HAL_PIXEL_FORMAT_RGB_565:
+            // Single plane, 16bpp.
+            bytesPerPixel = 2;
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height * bytesPerPixel;
+            pStride = bytesPerPixel;
+            rStride = buffer->stride * 2;
+            break;
+        case HAL_PIXEL_FORMAT_RGB_888:
+            // Single plane, 24bpp.
+            bytesPerPixel = 3;
+            LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height * bytesPerPixel;
+            pStride = bytesPerPixel;
+            rStride = buffer->stride * 3;
+            break;
+        default:
+            return BAD_VALUE;
+    }
+
+    *base = pData;
+    *size = dataSize;
+    *pixelStride = pStride;
+    *rowStride = rStride;
+
+    return OK;
+}
+
+status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage,
+        const Rect& rect, int fenceFd, LockedImage* outputImage) {
+    ALOGV("%s: Try to lock the GraphicBuffer", __FUNCTION__);
+
+    if (buffer == nullptr || outputImage == nullptr) {
+        ALOGE("Input BufferItem or output LockedImage is NULL!");
+        return BAD_VALUE;
+    }
+    if (isFormatOpaque(buffer->getPixelFormat())) {
+        ALOGE("Opaque format buffer is not lockable!");
+        return BAD_VALUE;
+    }
+
+    void* pData = NULL;
+    android_ycbcr ycbcr = android_ycbcr();
+    status_t res;
+    int format = buffer->getPixelFormat();
+    int flexFormat = format;
+    if (isPossiblyYUV(format)) {
+        res = buffer->lockAsyncYCbCr(inUsage, rect, &ycbcr, fenceFd);
+        pData = ycbcr.y;
+        flexFormat = HAL_PIXEL_FORMAT_YCbCr_420_888;
+    }
+
+    // lockAsyncYCbCr for YUV is unsuccessful.
+    if (pData == NULL) {
+        res = buffer->lockAsync(inUsage, rect, &pData, fenceFd);
+        if (res != OK) {
+            ALOGE("Lock buffer failed!");
+            return res;
+        }
+    }
+
+    outputImage->data = reinterpret_cast<uint8_t*>(pData);
+    outputImage->width = buffer->getWidth();
+    outputImage->height = buffer->getHeight();
+    outputImage->format = format;
+    outputImage->flexFormat = flexFormat;
+    outputImage->stride =
+            (ycbcr.y != NULL) ? static_cast<uint32_t>(ycbcr.ystride) : buffer->getStride();
+
+    outputImage->dataCb = reinterpret_cast<uint8_t*>(ycbcr.cb);
+    outputImage->dataCr = reinterpret_cast<uint8_t*>(ycbcr.cr);
+    outputImage->chromaStride = static_cast<uint32_t>(ycbcr.cstride);
+    outputImage->chromaStep = static_cast<uint32_t>(ycbcr.chroma_step);
+    ALOGV("%s: Successfully locked the image from the GraphicBuffer", __FUNCTION__);
+    // Crop, transform, scalingMode, timestamp, and frameNumber should be set by caller,
+    // and cann't be set them here.
+    return OK;
+}
+
+status_t lockImageFromBuffer(BufferItem* bufferItem, uint32_t inUsage,
+        int fenceFd, LockedImage* outputImage) {
+    ALOGV("%s: Try to lock the BufferItem", __FUNCTION__);
+    if (bufferItem == nullptr || outputImage == nullptr) {
+        ALOGE("Input BufferItem or output LockedImage is NULL!");
+        return BAD_VALUE;
+    }
+
+    status_t res = lockImageFromBuffer(bufferItem->mGraphicBuffer, inUsage, bufferItem->mCrop,
+            fenceFd, outputImage);
+    if (res != OK) {
+        ALOGE("%s: lock graphic buffer failed", __FUNCTION__);
+        return res;
+    }
+
+    outputImage->crop        = bufferItem->mCrop;
+    outputImage->transform   = bufferItem->mTransform;
+    outputImage->scalingMode = bufferItem->mScalingMode;
+    outputImage->timestamp   = bufferItem->mTimestamp;
+    outputImage->dataSpace   = bufferItem->mDataSpace;
+    outputImage->frameNumber = bufferItem->mFrameNumber;
+    ALOGV("%s: Successfully locked the image from the BufferItem", __FUNCTION__);
+    return OK;
+}
+
+int getBufferWidth(BufferItem* buffer) {
+    if (buffer == NULL) return -1;
+
+    if (!buffer->mCrop.isEmpty()) {
+        return buffer->mCrop.getWidth();
+    }
+
+    ALOGV("%s: buffer->mGraphicBuffer: %p", __FUNCTION__, buffer->mGraphicBuffer.get());
+    return buffer->mGraphicBuffer->getWidth();
+}
+
+int getBufferHeight(BufferItem* buffer) {
+    if (buffer == NULL) return -1;
+
+    if (!buffer->mCrop.isEmpty()) {
+        return buffer->mCrop.getHeight();
+    }
+
+    ALOGV("%s: buffer->mGraphicBuffer: %p", __FUNCTION__, buffer->mGraphicBuffer.get());
+    return buffer->mGraphicBuffer->getHeight();
+}
+
 }  // namespace android
 
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index a30e1be..3791ec9 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -25,6 +25,8 @@
 #include <JNIHelp.h>
 #include <utils/KeyedVector.h>
 #include <utils/String8.h>
+#include <gui/CpuConsumer.h>
+#include <camera3.h>
 
 namespace android {
 
@@ -69,6 +71,33 @@
         JNIEnv *env, jobjectArray keys, jobjectArray values,
         sp<AMessage> *msg);
 
+// -----------Utility functions used by ImageReader/Writer JNI-----------------
+
+typedef CpuConsumer::LockedBuffer LockedImage;
+
+bool usingRGBAToJpegOverride(int32_t imageFormat, int32_t containerFormat);
+
+int32_t applyFormatOverrides(int32_t imageFormat, int32_t containerFormat);
+
+uint32_t Image_getJpegSize(LockedImage* buffer, bool usingRGBAOverride);
+
+bool isFormatOpaque(int format);
+
+bool isPossiblyYUV(PixelFormat format);
+
+status_t getLockedImageInfo(LockedImage* buffer, int idx, int32_t containerFormat,
+        uint8_t **base, uint32_t *size, int *pixelStride, int *rowStride);
+
+status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage,
+        const Rect& rect, int fenceFd, LockedImage* outputImage);
+
+status_t lockImageFromBuffer(BufferItem* bufferItem, uint32_t inUsage,
+        int fenceFd, LockedImage* outputImage);
+
+int getBufferWidth(BufferItem *buffer);
+
+int getBufferHeight(BufferItem *buffer);
+
 };  // namespace android
 
 #endif //  _ANDROID_MEDIA_UTILS_H_
diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk
index 7c1142b..7e438a1 100644
--- a/media/tests/MediaFrameworkTest/Android.mk
+++ b/media/tests/MediaFrameworkTest/Android.mk
@@ -11,7 +11,6 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := easymocklib \
     mockito-target \
-    core-tests \
     android-support-test \
     android-ex-camera2
 
diff --git a/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml b/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml
index 990ce0b..a1c2910 100644
--- a/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml
+++ b/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml
@@ -18,8 +18,9 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingTop="30dp"
-    android:gravity="center"
+    android:paddingTop="24dp"
+    android:paddingStart="24dp"
+    android:paddingEnd="24dp"
     android:textAppearance="@android:style/TextAppearance.Material.Subhead"
     android:textColor="@*android:color/primary_text_default_material_light">
 </TextView>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index 79f6fa9..2aee569 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -66,7 +66,7 @@
         android:id="@+id/menu_sort"
         android:title="@string/menu_sort"
         android:icon="@drawable/ic_menu_sortby"
-        android:showAsAction="never">
+        android:showAsAction="ifRoom">
         <menu>
             <item
                 android:id="@+id/menu_sort_name"
diff --git a/packages/DocumentsUI/res/values-sw720dp-land/dimens.xml b/packages/DocumentsUI/res/values-sw720dp-land/dimens.xml
index fa11244..1b67ee5 100644
--- a/packages/DocumentsUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/DocumentsUI/res/values-sw720dp-land/dimens.xml
@@ -20,4 +20,5 @@
 
     <dimen name="list_divider_inset">80dp</dimen>
 
+    <dimen name="max_drawer_width">320dp</dimen>
 </resources>
diff --git a/packages/DocumentsUI/res/values-sw720dp/dimens.xml b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
index b5d1150..982b204 100644
--- a/packages/DocumentsUI/res/values-sw720dp/dimens.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
@@ -20,4 +20,5 @@
 
     <dimen name="list_item_padding">24dp</dimen>
 
+    <dimen name="max_drawer_width">320dp</dimen>
 </resources>
diff --git a/packages/DocumentsUI/res/values/dimens.xml b/packages/DocumentsUI/res/values/dimens.xml
index 9fc8a73..5af7da3 100644
--- a/packages/DocumentsUI/res/values/dimens.xml
+++ b/packages/DocumentsUI/res/values/dimens.xml
@@ -37,4 +37,5 @@
     <dimen name="dir_elevation">8dp</dimen>
     <dimen name="drag_shadow_size">120dp</dimen>
     <dimen name="grid_item_elevation">2dp</dimen>
+    <dimen name="max_drawer_width">280dp</dimen>
 </resources>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 6e1b30e..e2d1870 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -213,4 +213,9 @@
         <item quantity="one">Delete <xliff:g id="count" example="1">%1$d</xliff:g> file?</item>
         <item quantity="other">Delete <xliff:g id="count" example="3">%1$d</xliff:g> files?</item>
     </plurals>
+    <!-- Label text showing user how many items are selected. Can be one or more elements. -->
+    <plurals name="elements_selected">
+        <item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> selected</item>
+        <item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
+    </plurals>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 6efe9d1..c9d18b3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -154,7 +154,7 @@
         final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
 
         // Search uses backend ranking; no sorting, recents doesn't support sort.
-        sort.setVisible(!inRecents && !mSearchManager.isSearching());
+        sort.setEnabled(!inRecents && !mSearchManager.isSearching());
         sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible
         fileSize.setVisible(!mState.forceSize);
 
@@ -244,6 +244,7 @@
                 return true;
 
             case R.id.menu_search:
+                // SearchViewManager listens for this directly.
                 return false;
 
             case R.id.menu_sort_name:
@@ -366,6 +367,7 @@
         assert(canSearchRoot());
 
         reloadSearch(query);
+        invalidateOptionsMenu();
     }
 
     private void reloadSearch(String query) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Display.java b/packages/DocumentsUI/src/com/android/documentsui/Display.java
new file mode 100644
index 0000000..bae2d58
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/Display.java
@@ -0,0 +1,56 @@
+/*
+ * 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.documentsui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.TypedValue;
+
+/*
+ * Convenience class for getting display related attributes
+ */
+public final class Display {
+    /*
+     * Returns the screen width in pixels.
+     */
+    public static float screenWidth(Activity activity) {
+        Point size = new Point();
+        activity.getWindowManager().getDefaultDisplay().getSize(size);
+        return size.x;
+    }
+
+    /*
+     * Returns logical density of the display.
+     */
+    public static float density(Context context) {
+        return context.getResources().getDisplayMetrics().density;
+    }
+
+    /*
+     * Returns action bar height in pixels.
+     */
+    public static float actionBarHeight(Context context) {
+        int actionBarHeight = 0;
+        TypedValue tv = new TypedValue();
+        if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
+            actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,
+                    context.getResources().getDisplayMetrics());
+        }
+        return actionBarHeight;
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index 9fceff5..020f2c0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -16,10 +16,14 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
+
 import android.app.Activity;
+import android.content.Context;
 import android.support.v4.app.ActionBarDrawerToggle;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v4.widget.DrawerLayout.DrawerListener;
+import android.util.Log;
 import android.view.View;
 import android.widget.Toolbar;
 
@@ -30,6 +34,8 @@
  */
 abstract class DrawerController implements DrawerListener {
 
+    public static final String TAG = "DrawerController";
+
     abstract void setOpen(boolean open);
     abstract boolean isPresent();
     abstract boolean isOpen();
@@ -50,6 +56,8 @@
         View drawer = activity.findViewById(R.id.drawer_roots);
         Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
 
+        drawer.getLayoutParams().width = calculateDrawerWidth(activity);
+
         ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                 activity,
                 layout,
@@ -67,6 +75,19 @@
         return new DummyDrawerController();
     }
 
+    private static int calculateDrawerWidth(Activity activity) {
+        // Material design specification for navigation drawer:
+        // https://www.google.com/design/spec/patterns/navigation-drawer.html
+        float width = Display.screenWidth(activity) - Display.actionBarHeight(activity);
+        float maxWidth = activity.getResources().getDimension(R.dimen.max_drawer_width);
+        int finalWidth = (int) ((width > maxWidth ? maxWidth : width));
+
+        if (DEBUG)
+            Log.d(TAG, "Calculated drawer width:" + (finalWidth / Display.density(activity)));
+
+        return finalWidth;
+    }
+
     /**
      * Runtime controller that manages a real drawer.
      */
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 1967923..ca1b444 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -63,6 +63,7 @@
 import android.view.ActionMode;
 import android.view.DragEvent;
 import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -526,7 +527,8 @@
             getActivity().getWindow().setStatusBarColor(color.data);
 
             if (mActionMode != null) {
-                mActionMode.setTitle(String.valueOf(mSelected.size()));
+                mActionMode.setTitle(Shared.getQuantityString(getActivity(),
+                        R.plurals.elements_selected, mSelected.size()));
             }
         }
 
@@ -544,6 +546,8 @@
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+
             int size = mSelectionManager.getSelection().size();
             mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
             mode.setTitle(TextUtils.formatSelectedCount(size));
@@ -586,18 +590,17 @@
 
                 case R.id.menu_share:
                     shareDocuments(selection);
-                    mode.finish();
                     return true;
 
                 case R.id.menu_delete:
-                    // Exit selection mode first, so we avoid deselecting deleted documents.
-                    mode.finish();
-                    deleteDocuments(selection);
+                    // Pass mode along to the delete function so it can
+                    // end action mode when documents are deleted.
+                    // It won't end action mode if user cancels the delete.
+                    deleteDocuments(selection, mode);
                     return true;
 
                 case R.id.menu_copy_to:
                     transferDocuments(selection, FileOperationService.OPERATION_COPY);
-                    mode.finish();
                     return true;
 
                 case R.id.menu_move_to:
@@ -615,8 +618,10 @@
                     return true;
 
                 case R.id.menu_rename:
-                    renameDocuments(selection);
+                    // Exit selection mode first, so we avoid deselecting deleted
+                    // (renamed) documents.
                     mode.finish();
+                    renameDocuments(selection);
                     return true;
 
                 default:
@@ -700,7 +705,7 @@
         }.execute(selected);
     }
 
-    private void deleteDocuments(final Selection selected) {
+    private void deleteDocuments(final Selection selected, final ActionMode mode) {
         assert(!selected.isEmpty());
 
         final DocumentInfo srcParent = getDisplayState().stack.peek();
@@ -732,7 +737,15 @@
                          android.R.string.yes,
                          new DialogInterface.OnClickListener() {
                             public void onClick(DialogInterface dialog, int id) {
-                                // Hide the files in the UI.
+                                // Finish selection mode first which clears selection so we
+                                // don't end up trying to deselect deleted documents.
+                                // This is done here, rather in the onActionItemClicked
+                                // so we can avoid de-selecting items in the case where
+                                // the user cancels the delete.
+                                mode.finish();
+                                // Hide the files in the UI...since the operation
+                                // might be queued up on FileOperationService.
+                                // We're walking a line here.
                                 mAdapter.hide(selected.getAll());
                                 FileOperations.delete(
                                         getActivity(), docs, srcParent, getDisplayState().stack);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
index 5f33b32..9a06807 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
@@ -16,9 +16,8 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.StubProvider.ROOT_0_ID;
-
 import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
 
 @LargeTest
 public class RenameDocumentUiTest extends ActivityTest<FilesActivity> {
@@ -73,6 +72,7 @@
         device.pressBack();
     }
 
+    @Suppress
     public void testRenameFile_OkButton() throws Exception {
         bots.directory.selectDocument(fileName1);
         bots.main.openOverflowMenu();
@@ -101,6 +101,7 @@
         bots.directory.assertDocumentsCount(4);
     }
 
+    @Suppress
     public void testRenameFile_Cancel() throws Exception {
         bots.directory.selectDocument(fileName1);
         bots.main.openOverflowMenu();
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ae2c6e7..ef397f6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -677,7 +677,7 @@
     <!-- Services settings screen, setting option summary for the user to go to the screen to view running services  -->
     <string name="runningservices_settings_summary">View and control currently running services</string>
 
-    <!-- Developer settings: enable WebView multiprocess name [CHAR LIMIT=30] -->
+    <!-- Developer settings: enable WebView multiprocess name [CHAR LIMIT=50] -->
     <string name="enable_webview_multiprocess">Enable multiprocess WebView</string>
     <!-- Developer settings: enable WebView multiprocess summary [CHAR LIMIT=60] -->
     <string name="enable_webview_multiprocess_desc">Run WebView renderers in an isolated process.</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
deleted file mode 100644
index 4c0450e..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.settingslib;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.support.v7.preference.DropDownPreference;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
-public class RestrictedDropDownPreference extends DropDownPreference {
-    private Spinner mSpinner;
-    private final Drawable mRestrictedPadlock;
-    private final int mRestrictedPadlockPadding;
-    private List<RestrictedItem> mRestrictedItems = new ArrayList<>();
-
-    public RestrictedDropDownPreference(Context context) {
-        this(context, null);
-    }
-
-    public RestrictedDropDownPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mRestrictedPadlock = RestrictedLockUtils.getRestrictedPadlock(context);
-        mRestrictedPadlockPadding = context.getResources().getDimensionPixelSize(
-                R.dimen.restricted_icon_padding);
-    }
-
-    private final OnItemSelectedListener mItemSelectedListener = new OnItemSelectedListener() {
-        @Override
-        public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
-            if (position >= 0) {
-                String value = getEntryValues()[position].toString();
-                RestrictedItem item = getRestrictedItemForEntryValue(value);
-                if (item != null) {
-                    RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
-                            item.enforcedAdmin);
-                    mSpinner.setSelection(findIndexOfValue(getValue()));
-                } else if (!value.equals(getValue()) && callChangeListener(value)) {
-                    setValue(value);
-                }
-            }
-        }
-
-        @Override
-        public void onNothingSelected(AdapterView<?> parent) {
-            // noop
-        }
-    };
-
-    @Override
-    protected ArrayAdapter createAdapter() {
-        return new RestrictedArrayItemAdapter(getContext());
-    }
-
-    @Override
-    public void setValue(String value) {
-        if (getRestrictedItemForEntryValue(value) != null) {
-            return;
-        }
-        super.setValue(value);
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder view) {
-        super.onBindViewHolder(view);
-        mSpinner = (Spinner) view.itemView.findViewById(R.id.spinner);
-        mSpinner.setOnItemSelectedListener(mItemSelectedListener);
-    }
-
-    private class RestrictedArrayItemAdapter extends ArrayAdapter<String> {
-        public RestrictedArrayItemAdapter(Context context) {
-            super(context, R.layout.spinner_dropdown_restricted_item);
-        }
-
-        @Override
-        public View getDropDownView(int position, View convertView, ViewGroup parent) {
-            TextView view = (TextView) super.getView(position, convertView, parent);
-            CharSequence entry = getItem(position);
-            boolean isEntryRestricted = isRestrictedForEntry(entry);
-            RestrictedLockUtils.setTextViewPadlock(getContext(), view, isEntryRestricted);
-            view.setEnabled(!isEntryRestricted);
-            return view;
-        }
-    }
-
-    private boolean isRestrictedForEntry(CharSequence entry) {
-        if (entry == null) {
-            return false;
-        }
-        for (RestrictedItem item : mRestrictedItems) {
-            if (entry.equals(item.entry)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private RestrictedItem getRestrictedItemForEntryValue(CharSequence entryValue) {
-        if (entryValue == null) {
-            return null;
-        }
-        for (RestrictedItem item : mRestrictedItems) {
-            if (entryValue.equals(item.entryValue)) {
-                return item;
-            }
-        }
-        return null;
-    }
-
-    public void addRestrictedItem(RestrictedItem item) {
-        mRestrictedItems.add(item);
-    }
-
-    public static class RestrictedItem {
-        public CharSequence entry;
-        public CharSequence entryValue;
-        public EnforcedAdmin enforcedAdmin;
-
-        public RestrictedItem(CharSequence entry, CharSequence entryValue,
-                EnforcedAdmin enforcedAdmin) {
-            this.entry = entry;
-            this.entryValue = entryValue;
-            this.enforcedAdmin = enforcedAdmin;
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 51d8ca0..978ca94 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -155,6 +155,9 @@
     <!-- Default for Settings.Secure.LONG_PRESS_TIMEOUT_MILLIS -->
     <integer name="def_long_press_timeout_millis">500</integer>
 
+    <!-- Default for Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD -->
+    <bool name="def_show_ime_with_hard_keyboard">false</bool>
+
     <!-- Default for Settings.System.POINTER_SPEED -->
     <integer name="def_pointer_speed">0</integer>
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a424d55..987b5ea 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1671,16 +1671,16 @@
 
         private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
                 SQLiteDatabase database, int userId) {
-            // Move over the global settings if owner.
-            if (userId == UserHandle.USER_SYSTEM) {
-                final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId);
-                ensureSettingsStateLocked(globalKey);
-                SettingsState globalSettings = mSettingsStates.get(globalKey);
-                migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);
-                globalSettings.persistSyncLocked();
-            }
+            // Move over the system settings.
+            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+            ensureSettingsStateLocked(systemKey);
+            SettingsState systemSettings = mSettingsStates.get(systemKey);
+            migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
+            systemSettings.persistSyncLocked();
 
             // Move over the secure settings.
+            // Do this after System settings, since this is the first thing we check when deciding
+            // to skip over migration from db to xml for a secondary user.
             final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
             ensureSettingsStateLocked(secureKey);
             SettingsState secureSettings = mSettingsStates.get(secureKey);
@@ -1688,12 +1688,16 @@
             ensureSecureSettingAndroidIdSetLocked(secureSettings);
             secureSettings.persistSyncLocked();
 
-            // Move over the system settings.
-            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
-            ensureSettingsStateLocked(systemKey);
-            SettingsState systemSettings = mSettingsStates.get(systemKey);
-            migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
-            systemSettings.persistSyncLocked();
+            // Move over the global settings if owner.
+            // Do this last, since this is the first thing we check when deciding
+            // to skip over migration from db to xml for owner user.
+            if (userId == UserHandle.USER_SYSTEM) {
+                final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+                ensureSettingsStateLocked(globalKey);
+                SettingsState globalSettings = mSettingsStates.get(globalKey);
+                migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);
+                globalSettings.persistSyncLocked();
+            }
 
             // Drop the database as now all is moved and persisted.
             if (DROP_DATABASE_ON_MIGRATION) {
@@ -1936,7 +1940,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 124;
+            private static final int SETTINGS_VERSION = 125;
 
             private final int mUserId;
 
@@ -2116,6 +2120,22 @@
                     currentVersion = 124;
                 }
 
+                if (currentVersion == 124) {
+                    // Version 124: allow OEMs to set a default value for whether IME should be
+                    // shown when a physical keyboard is connected.
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    Setting currentSetting = secureSettings.getSettingLocked(
+                            Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
+                    if (currentSetting == null) {
+                        secureSettings.insertSettingLocked(
+                                Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+                                getContext().getResources().getBoolean(
+                                        R.bool.def_show_ime_with_hard_keyboard) ? "1" : "0",
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+                    currentVersion = 125;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 // Return the current version.
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 0fad113..1c6a071 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -56,7 +56,6 @@
 import android.app.Service;
 import android.content.ClipData;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -206,7 +205,7 @@
         mMainHandler = new ServiceHandler("BugreportProgressServiceMainThread");
         mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
 
-        mScreenshotsDir = new File(new ContextWrapper(mContext).getFilesDir(), SCREENSHOT_DIR);
+        mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR);
         if (!mScreenshotsDir.exists()) {
             Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
             if (!mScreenshotsDir.mkdir()) {
@@ -505,9 +504,9 @@
             Log.d(TAG, "Removing ID " + id);
             mProcesses.remove(id);
         }
-        stopSelfWhenDone();
         Log.v(TAG, "stopProgress(" + id + "): cancel notification");
         NotificationManager.from(mContext).cancel(TAG, id);
+        stopSelfWhenDone();
     }
 
     /**
@@ -877,6 +876,8 @@
             info = sharedInfo;
             Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
                     + mProcesses + "), using info from intent instead (" + info + ")");
+        } else {
+            Log.v(TAG, "shareBugReport(): id " + id + " info = " + info);
         }
 
         addDetailsToZipFile(mContext, info);
@@ -1532,6 +1533,7 @@
                 final File newFile;
                 if (!newName.equals(oldName)) {
                     final File renamedFile = new File(screenshotDir, newName);
+                    Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
                     newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
                 } else {
                     Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 8b337ea..c1fe1a8 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -33,4 +33,10 @@
         android:layout_height="match_parent"
         android:src="@android:color/white"
         android:visibility="gone" />
+    <com.android.systemui.screenshot.ScreenshotSelectorView
+        android:id="@+id/global_screenshot_selector"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        android:pointerShape="crosshair"/>
 </FrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java
index 86bea87..bad739fd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java
@@ -36,6 +36,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "ACC";
+    }
+
+    @Override
     public void onTouchEvent(MotionEvent event) {
         int action = event.getActionMasked();
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
index dba731a..526e5fa 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
@@ -54,6 +54,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "ANG";
+    }
+
+    @Override
     public void onTouchEvent(MotionEvent event) {
         int action = event.getActionMasked();
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index 89d20de..cb761a9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -48,4 +48,6 @@
      */
     public void onSensorChanged(SensorEvent event) {
     }
+
+    public abstract String getTag();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java
index 299d0e3..610e219 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java
@@ -25,6 +25,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "DIR";
+    }
+
+    @Override
     public float getFalseTouchEvaluation(int type, Stroke stroke) {
         Point firstPoint = stroke.getPoints().get(0);
         Point lastPoint = stroke.getPoints().get(stroke.getPoints().size() - 1);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java
index 8924694..77fda20 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java
@@ -25,6 +25,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "DUR";
+    }
+
+    @Override
     public float getFalseTouchEvaluation(int type, Stroke stroke) {
         return DurationCountEvaluator.evaluate(stroke.getDurationSeconds() / stroke.getCount());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java
index 78bc0dd..de8a188 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java
@@ -24,6 +24,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "END_LNGTH";
+    }
+
+    @Override
     public float getFalseTouchEvaluation(int type, Stroke stroke) {
         return EndPointLengthEvaluator.evaluate(stroke.getEndPointLength());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
index 652d969..9b6ddc8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
@@ -26,6 +26,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "END_RTIO";
+    }
+
+    @Override
     public float getFalseTouchEvaluation(int type, Stroke stroke) {
         float ratio;
         if (stroke.getTotalLength() == 0.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
new file mode 100644
index 0000000..1338d9d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
@@ -0,0 +1,166 @@
+/*
+ * 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.systemui.classifier;
+
+import android.app.ActivityThread;
+import android.app.Application;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Keeps track of interesting falsing data.
+ *
+ * By default the log only gets collected on userdebug builds. To turn it on on user:
+ *  adb shell setprop debug.falsing_log true
+ *
+ * The log gets dumped as part of the SystemUI services. To dump on demand:
+ *  adb shell dumpsys activity service com.android.systemui SystemBars | grep -A 999 FALSING | less
+ *
+ * To dump into logcat:
+ *  adb shell setprop debug.falsing_logcat true
+ *
+ * To adjust the log buffer size:
+ *  adb shell setprop debug.falsing_log_size 200
+ */
+public class FalsingLog {
+    public static final boolean ENABLED = SystemProperties.getBoolean("debug.falsing_log",
+            Build.IS_DEBUGGABLE);
+    private static final boolean LOGCAT = SystemProperties.getBoolean("debug.falsing_logcat",
+            false);
+
+    public static final boolean VERBOSE = false;
+
+    private static final int MAX_SIZE = SystemProperties.getInt("debug.falsing_log_size", 100);
+
+    private static final String TAG = "FalsingLog";
+
+    private final ArrayDeque<String> mLog = new ArrayDeque<>(MAX_SIZE);
+    private final SimpleDateFormat mFormat = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
+
+    private static FalsingLog sInstance;
+
+    private FalsingLog() {
+    }
+
+    public static void v(String tag, String s) {
+        if (!VERBOSE) {
+            return;
+        }
+        if (LOGCAT) {
+            Log.v(TAG, tag + "\t" + s);
+        }
+        log("V", tag, s);
+    }
+
+    public static void i(String tag, String s) {
+        if (LOGCAT) {
+            Log.i(TAG, tag + "\t" + s);
+        }
+        log("I", tag, s);
+    }
+
+    public static void w(String tag, String s) {
+        if (LOGCAT) {
+            Log.w(TAG, tag + "\t" + s);
+        }
+        log("W", tag, s);
+    }
+
+    public static void e(String tag, String s) {
+        if (LOGCAT) {
+            Log.e(TAG, tag + "\t" + s);
+        }
+        log("E", tag, s);
+    }
+
+    public static synchronized void log(String level, String tag, String s) {
+        if (!ENABLED) {
+            return;
+        }
+        if (sInstance == null) {
+            sInstance = new FalsingLog();
+        }
+
+        if (sInstance.mLog.size() >= MAX_SIZE) {
+            sInstance.mLog.removeFirst();
+        }
+        String entry = new StringBuilder().append(sInstance.mFormat.format(new Date()))
+            .append(" ").append(level).append(" ")
+            .append(tag).append(" ").append(s).toString();
+        sInstance.mLog.add(entry);
+    }
+
+    public static synchronized void dump(PrintWriter pw) {
+        pw.println("FALSING LOG:");
+        if (!ENABLED) {
+            pw.println("Disabled, to enable: setprop debug.falsing_log 1");
+            pw.println();
+            return;
+        }
+        if (sInstance == null || sInstance.mLog.isEmpty()) {
+            pw.println("<empty>");
+            pw.println();
+            return;
+        }
+        for (String s : sInstance.mLog) {
+            pw.println(s);
+        }
+        pw.println();
+    }
+
+    public static synchronized void wtf(String tag, String s) {
+        if (!ENABLED) {
+            return;
+        }
+        e(tag, s);
+
+        Application application = ActivityThread.currentApplication();
+        String fileMessage = "";
+        if (Build.IS_DEBUGGABLE && application != null) {
+            File f = new File(application.getDataDir(), "falsing-"
+                    + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".txt");
+            PrintWriter pw = null;
+            try {
+                pw = new PrintWriter(f);
+                dump(pw);
+                pw.close();
+                fileMessage = "Log written to " + f.getAbsolutePath();
+            } catch (IOException e) {
+                Log.e(TAG, "Unable to write falsing log", e);
+            } finally {
+                if (pw != null) {
+                    pw.close();
+                }
+            }
+        } else {
+            Log.e(TAG, "Unable to write log, build must be debuggable.");
+        }
+
+        Log.wtf(TAG, tag + " " + s + "; " + fileMessage);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index c09376b..937f7d3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -31,6 +31,8 @@
 import com.android.systemui.analytics.DataCollector;
 import com.android.systemui.statusbar.StatusBarState;
 
+import java.io.PrintWriter;
+
 /**
  * When the phone is locked, listens to touch, sensor and phone events and sends them to
  * DataCollector and HumanInteractionClassifier.
@@ -102,8 +104,14 @@
     }
 
     private boolean shouldSessionBeActive() {
-        return isEnabled() && mScreenOn &&
-                (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED);
+        if (FalsingLog.ENABLED && FalsingLog.VERBOSE)
+            FalsingLog.v("shouldBeActive", new StringBuilder()
+                    .append("enabled=").append(isEnabled() ? 1 : 0)
+                    .append(" mScreenOn=").append(mScreenOn ? 1 : 0)
+                    .append(" mState=").append(StatusBarState.toShortString(mState))
+                    .toString()
+            );
+        return isEnabled() && mScreenOn && (mState == StatusBarState.KEYGUARD);
     }
 
     private boolean sessionEntrypoint() {
@@ -122,6 +130,9 @@
     }
 
     private void onSessionStart() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onSessionStart", "classifierEnabled=" + isClassiferEnabled());
+        }
         mBouncerOn = false;
         mSessionActive = true;
 
@@ -154,6 +165,16 @@
      * @return true if the classifier determined that this is not a human interacting with the phone
      */
     public boolean isFalseTouch() {
+        if (FalsingLog.ENABLED) {
+            if (!mSessionActive) {
+                FalsingLog.wtf("isFalseTouch", new StringBuilder()
+                        .append("Session is not active, yet there's a query for a false touch.")
+                        .append(" enabled=").append(isEnabled() ? 1 : 0)
+                        .append(" mScreenOn=").append(mScreenOn ? 1 : 0)
+                        .append(" mState=").append(StatusBarState.toShortString(mState))
+                        .toString());
+            }
+        }
         return mHumanInteractionClassifier.isFalseTouch();
     }
 
@@ -173,6 +194,12 @@
     }
 
     public void setStatusBarState(int state) {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("setStatusBarState", new StringBuilder()
+                    .append("from=").append(StatusBarState.toShortString(mState))
+                    .append(" to=").append(StatusBarState.toShortString(state))
+                    .toString());
+        }
         mState = state;
         if (shouldSessionBeActive()) {
             sessionEntrypoint();
@@ -182,6 +209,11 @@
     }
 
     public void onScreenTurningOn() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onScreenTurningOn", new StringBuilder()
+                    .append("from=").append(mScreenOn ? 1 : 0)
+                    .toString());
+        }
         mScreenOn = true;
         if (sessionEntrypoint()) {
             mDataCollector.onScreenTurningOn();
@@ -189,6 +221,11 @@
     }
 
     public void onScreenOnFromTouch() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onScreenOnFromTouch", new StringBuilder()
+                    .append("from=").append(mScreenOn ? 1 : 0)
+                    .toString());
+        }
         mScreenOn = true;
         if (sessionEntrypoint()) {
             mDataCollector.onScreenOnFromTouch();
@@ -196,17 +233,30 @@
     }
 
     public void onScreenOff() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onScreenOff", new StringBuilder()
+                    .append("from=").append(mScreenOn ? 1 : 0)
+                    .toString());
+        }
         mDataCollector.onScreenOff();
         mScreenOn = false;
         sessionExitpoint(false /* force */);
     }
 
     public void onSucccessfulUnlock() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onSucccessfulUnlock", "");
+        }
         mDataCollector.onSucccessfulUnlock();
         sessionExitpoint(true /* force */);
     }
 
     public void onBouncerShown() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onBouncerShown", new StringBuilder()
+                    .append("from=").append(mBouncerOn ? 1 : 0)
+                    .toString());
+        }
         if (!mBouncerOn) {
             mBouncerOn = true;
             mDataCollector.onBouncerShown();
@@ -214,6 +264,11 @@
     }
 
     public void onBouncerHidden() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onBouncerHidden", new StringBuilder()
+                    .append("from=").append(mBouncerOn ? 1 : 0)
+                    .toString());
+        }
         if (mBouncerOn) {
             mBouncerOn = false;
             mDataCollector.onBouncerHidden();
@@ -221,6 +276,9 @@
     }
 
     public void onQsDown() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onQsDown", "");
+        }
         mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS);
         mDataCollector.onQsDown();
     }
@@ -230,6 +288,9 @@
     }
 
     public void onTrackingStarted() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onTrackingStarted", "");
+        }
         mHumanInteractionClassifier.setType(Classifier.UNLOCK);
         mDataCollector.onTrackingStarted();
     }
@@ -251,6 +312,9 @@
     }
 
     public void onNotificatonStartDraggingDown() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onNotificatonStartDraggingDown", "");
+        }
         mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DRAG_DOWN);
         mDataCollector.onNotificatonStartDraggingDown();
     }
@@ -264,6 +328,9 @@
     }
 
     public void onNotificatonStartDismissing() {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onNotificatonStartDismissing", "");
+        }
         mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS);
         mDataCollector.onNotificatonStartDismissing();
     }
@@ -281,6 +348,9 @@
     }
 
     public void onAffordanceSwipingStarted(boolean rightCorner) {
+        if (FalsingLog.ENABLED) {
+            FalsingLog.i("onAffordanceSwipingStarted", "");
+        }
         if (rightCorner) {
             mHumanInteractionClassifier.setType(Classifier.RIGHT_AFFORDANCE);
         } else {
@@ -311,4 +381,14 @@
             mHumanInteractionClassifier.onTouchEvent(event);
         }
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println("FALSING MANAGER");
+        pw.print("classifierEnabled="); pw.println(isClassiferEnabled() ? 1 : 0);
+        pw.print("mSessionActive="); pw.println(mSessionActive ? 1 : 0);
+        pw.print("mBouncerOn="); pw.println(mSessionActive ? 1 : 0);
+        pw.print("mState="); pw.println(StatusBarState.toShortString(mState));
+        pw.print("mScreenOn="); pw.println(mScreenOn ? 1 : 0);
+        pw.println();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
index 45eb9ad..5e35d76 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
@@ -23,6 +23,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.view.MotionEvent;
 
 import java.util.ArrayDeque;
@@ -43,14 +44,12 @@
     private final Handler mHandler = new Handler();
     private final Context mContext;
 
-    private ArrayList<StrokeClassifier> mStrokeClassifiers = new ArrayList<>();
-    private ArrayList<GestureClassifier> mGestureClassifiers = new ArrayList<>();
-    private ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
-    private final int mStrokeClassifiersSize;
-    private final int mGestureClassifiersSize;
+    private final StrokeClassifier[] mStrokeClassifiers;
+    private final GestureClassifier[] mGestureClassifiers;
+    private final ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
+    private final HistoryEvaluator mHistoryEvaluator;
     private final float mDpi;
 
-    private HistoryEvaluator mHistoryEvaluator;
     private boolean mEnableClassifier = false;
     private int mCurrentType = Classifier.GENERIC;
 
@@ -68,25 +67,27 @@
         // If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi
         // were to be used separately. Due negligible differences in xdpi and ydpi we can just
         // take the average.
+        // TODO: make this respect DPI changes.
         mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
         mClassifierData = new ClassifierData(mDpi);
         mHistoryEvaluator = new HistoryEvaluator();
 
-        mStrokeClassifiers.add(new AnglesClassifier(mClassifierData));
-        mStrokeClassifiers.add(new SpeedClassifier(mClassifierData));
-        mStrokeClassifiers.add(new DurationCountClassifier(mClassifierData));
-        mStrokeClassifiers.add(new EndPointRatioClassifier(mClassifierData));
-        mStrokeClassifiers.add(new EndPointLengthClassifier(mClassifierData));
-        mStrokeClassifiers.add(new AccelerationClassifier(mClassifierData));
-        mStrokeClassifiers.add(new SpeedAnglesClassifier(mClassifierData));
-        mStrokeClassifiers.add(new LengthCountClassifier(mClassifierData));
-        mStrokeClassifiers.add(new DirectionClassifier(mClassifierData));
+        mStrokeClassifiers = new StrokeClassifier[]{
+                new AnglesClassifier(mClassifierData),
+                new SpeedClassifier(mClassifierData),
+                new DurationCountClassifier(mClassifierData),
+                new EndPointRatioClassifier(mClassifierData),
+                new EndPointLengthClassifier(mClassifierData),
+                new AccelerationClassifier(mClassifierData),
+                new SpeedAnglesClassifier(mClassifierData),
+                new LengthCountClassifier(mClassifierData),
+                new DirectionClassifier(mClassifierData),
+        };
 
-        mGestureClassifiers.add(new PointerCountClassifier(mClassifierData));
-        mGestureClassifiers.add(new ProximityClassifier(mClassifierData));
-
-        mStrokeClassifiersSize = mStrokeClassifiers.size();
-        mGestureClassifiersSize = mGestureClassifiers.size();
+        mGestureClassifiers = new GestureClassifier[] {
+                new PointerCountClassifier(mClassifierData),
+                new ProximityClassifier(mClassifierData)
+        };
 
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(HIC_ENABLE), false,
@@ -150,21 +151,30 @@
     private void addTouchEvent(MotionEvent event) {
         mClassifierData.update(event);
 
-        for (int i = 0; i < mStrokeClassifiersSize; i++) {
-            mStrokeClassifiers.get(i).onTouchEvent(event);
+        for (StrokeClassifier c : mStrokeClassifiers) {
+            c.onTouchEvent(event);
         }
 
-        for (int i = 0; i < mGestureClassifiersSize; i++) {
-            mGestureClassifiers.get(i).onTouchEvent(event);
+        for (GestureClassifier c : mGestureClassifiers) {
+            c.onTouchEvent(event);
         }
 
         int size = mClassifierData.getEndingStrokes().size();
         for (int i = 0; i < size; i++) {
             Stroke stroke = mClassifierData.getEndingStrokes().get(i);
             float evaluation = 0.0f;
-            for (int j = 0; j < mStrokeClassifiersSize; j++) {
-                evaluation += mStrokeClassifiers.get(j).getFalseTouchEvaluation(
-                        mCurrentType, stroke);
+            StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("stroke") : null;
+            for (StrokeClassifier c : mStrokeClassifiers) {
+                float e = c.getFalseTouchEvaluation(mCurrentType, stroke);
+                if (FalsingLog.ENABLED) {
+                    String tag = c.getTag();
+                    sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
+                }
+                evaluation += e;
+            }
+
+            if (FalsingLog.ENABLED) {
+                FalsingLog.i(" addTouchEvent", sb.toString());
             }
             mHistoryEvaluator.addStroke(evaluation);
         }
@@ -172,8 +182,17 @@
         int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
             float evaluation = 0.0f;
-            for (int i = 0; i < mGestureClassifiersSize; i++) {
-                evaluation += mGestureClassifiers.get(i).getFalseTouchEvaluation(mCurrentType);
+            StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("gesture") : null;
+            for (GestureClassifier c : mGestureClassifiers) {
+                float e = c.getFalseTouchEvaluation(mCurrentType);
+                if (FalsingLog.ENABLED) {
+                    String tag = c.getTag();
+                    sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
+                }
+                evaluation += e;
+            }
+            if (FalsingLog.ENABLED) {
+                FalsingLog.i(" addTouchEvent", sb.toString());
             }
             mHistoryEvaluator.addGesture(evaluation);
             setType(Classifier.GENERIC);
@@ -184,18 +203,25 @@
 
     @Override
     public void onSensorChanged(SensorEvent event) {
-        for (int i = 0; i < mStrokeClassifiers.size(); i++) {
-            mStrokeClassifiers.get(i).onSensorChanged(event);
+        for (Classifier c : mStrokeClassifiers) {
+            c.onSensorChanged(event);
         }
 
-        for (int i = 0; i < mGestureClassifiers.size(); i++) {
-            mGestureClassifiers.get(i).onSensorChanged(event);
+        for (Classifier c : mGestureClassifiers) {
+            c.onSensorChanged(event);
         }
     }
 
     public boolean isFalseTouch() {
         if (mEnableClassifier) {
-            return mHistoryEvaluator.getEvaluation() >= 5.0f;
+            float evaluation = mHistoryEvaluator.getEvaluation();
+            boolean result = evaluation >= 5.0f;
+            if (FalsingLog.ENABLED) {
+                FalsingLog.i("isFalseTouch", new StringBuilder()
+                        .append("eval=").append(evaluation).append(" result=")
+                        .append(result ? 1 : 0).toString());
+            }
+            return result;
         }
         return false;
     }
@@ -203,4 +229,9 @@
     public boolean isEnabled() {
         return mEnableClassifier;
     }
+
+    @Override
+    public String getTag() {
+        return "HIC";
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java
index cedf467..53678a6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java
@@ -28,6 +28,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "LEN_CNT";
+    }
+
+    @Override
     public float getFalseTouchEvaluation(int type, Stroke stroke) {
         return LengthCountEvaluator.evaluate(stroke.getTotalLength()
                 / Math.max(1.0f, stroke.getCount() - 2));
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
index 5097b63..136c433 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
@@ -29,6 +29,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "PTR_CNT";
+    }
+
+    @Override
     public void onTouchEvent(MotionEvent event) {
         int action = event.getActionMasked();
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
index 6995064..62adfc8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
@@ -35,6 +35,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "PROX";
+    }
+
+    @Override
     public void onSensorChanged(SensorEvent event) {
         if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
             update(event.values[0] < event.sensor.getMaximumRange(), event.timestamp);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
index d58274d..6df72b1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
@@ -41,6 +41,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "SPD_ANG";
+    }
+
+    @Override
     public void onTouchEvent(MotionEvent event) {
         int action = event.getActionMasked();
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java
index 81b78c7..01fcc37 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java
@@ -27,6 +27,11 @@
     }
 
     @Override
+    public String getTag() {
+        return "SPD";
+    }
+
+    @Override
     public float getFalseTouchEvaluation(int type, Stroke stroke) {
         float duration = (float) stroke.getDurationNanos() / NANOS_TO_SECONDS;
         if (duration == 0.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 6137349..c8c71cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -14,11 +14,11 @@
 
 package com.android.systemui.qs;
 
+import android.graphics.Path;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnLayoutChangeListener;
-import android.view.animation.PathInterpolator;
 import android.widget.TextView;
 import com.android.systemui.qs.PagedTileLayout.PageListener;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
@@ -40,9 +40,6 @@
     private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim";
     private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
 
-    public static final PathInterpolator TRANSLATION_Y_INTERPOLATOR =
-            new PathInterpolator(.1f, .3f, 1, 1);
-
     public static final float EXPANDED_TILE_DELAY = .7f;
 
     private final ArrayList<View> mAllViews = new ArrayList<>();
@@ -56,7 +53,7 @@
     private boolean mOnFirstPage = true;
     private TouchAnimator mFirstPageAnimator;
     private TouchAnimator mFirstPageDelayedAnimator;
-    private TouchAnimator mTranslationYAnimator;
+    private TouchAnimator mTranslationAnimator;
     private TouchAnimator mNonfirstPageAnimator;
 
     private boolean mOnKeyguard;
@@ -110,7 +107,7 @@
                 clearAnimationState();
             }
         } else if (MOVE_FULL_ROWS.equals(key)) {
-            mFullRows = newValue != null && Integer.parseInt(newValue) != 0;
+            mFullRows = newValue == null || Integer.parseInt(newValue) != 0;
         } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
             mNumQuickTiles = QuickQSPanel.getNumQuickTiles(mQsContainer.getContext());
             clearAnimationState();
@@ -129,6 +126,7 @@
 
     private void updateAnimators() {
         TouchAnimator.Builder firstPageBuilder = new Builder();
+        TouchAnimator.Builder translationXBuilder = new Builder();
         TouchAnimator.Builder translationYBuilder = new Builder();
         TouchAnimator.Builder firstPageDelayedBuilder = new Builder();
         Collection<QSTile<?>> tiles = mQsPanel.getHost().getTiles();
@@ -138,7 +136,6 @@
         int lastYDiff = 0;
         firstPageDelayedBuilder.setStartDelay(EXPANDED_TILE_DELAY);
         firstPageBuilder.setListener(this);
-        translationYBuilder.setInterpolator(TRANSLATION_Y_INTERPOLATOR);
         // Fade in the tiles/labels as we reach the final position.
         firstPageDelayedBuilder.addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1);
         mAllViews.clear();
@@ -158,7 +155,7 @@
                 final int yDiff = loc2[1] - loc1[1];
                 lastYDiff = yDiff;
                 // Move the quick tile right from its location to the new one.
-                firstPageBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
+                translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
                 translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
 
                 // Counteract the parent translation on the tile. So we have a static base to
@@ -167,7 +164,7 @@
 
                 // Move the real tile's label from the quick tile position to its final
                 // location.
-                firstPageBuilder.addFloat(label, "translationX", -xDiff, 0);
+                translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
                 translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
 
                 mTopFiveQs.add(tileIcon);
@@ -188,7 +185,13 @@
         if (mAllowFancy) {
             mFirstPageAnimator = firstPageBuilder.build();
             mFirstPageDelayedAnimator = firstPageDelayedBuilder.build();
-            mTranslationYAnimator = translationYBuilder.build();
+            Path path = new Path();
+            path.moveTo(0, 0);
+            path.cubicTo(0, 0, 0, 1, 1, 1);
+            mTranslationAnimator = new TouchAnimator.Builder()
+                    .addPath(translationXBuilder.build(), translationYBuilder.build(),
+                            "position", "position", path)
+                    .build();
         }
         mNonfirstPageAnimator = new TouchAnimator.Builder()
                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
@@ -226,7 +229,7 @@
             mQuickQsPanel.setAlpha(1);
             mFirstPageAnimator.setPosition(position);
             mFirstPageDelayedAnimator.setPosition(position);
-            mTranslationYAnimator.setPosition(position);
+            mTranslationAnimator.setPosition(position);
         } else {
             mNonfirstPageAnimator.setPosition(position);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
index 35ade58..db17245 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -14,7 +14,11 @@
 
 package com.android.systemui.qs;
 
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.util.FloatProperty;
 import android.util.MathUtils;
+import android.util.Pair;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -74,6 +78,19 @@
         }
     }
 
+    private static final FloatProperty<TouchAnimator> POSITION =
+            new FloatProperty<TouchAnimator>("position") {
+        @Override
+        public void setValue(TouchAnimator touchAnimator, float value) {
+            touchAnimator.setPosition(value);
+        }
+
+        @Override
+        public Float get(TouchAnimator touchAnimator) {
+            return touchAnimator.mLastT;
+        }
+    };
+
     public static class ListenerAdapter implements Listener {
         @Override
         public void onAnimationAtStart() { }
@@ -124,6 +141,19 @@
             return this;
         }
 
+        public Builder addPath(Object target, String xProp, String yProp,
+                Path path) {
+            return addPath(target, target, xProp, yProp, path);
+        }
+
+        public Builder addPath(Object xTarget, Object yTarget, String xProp, String yProp,
+                Path path) {
+            add(new Pair<>(xTarget, yTarget),
+                    KeyframeSet.ofPath(getProperty(xTarget, xProp, float.class),
+                    getProperty(yTarget, yProp, float.class), path));
+            return this;
+        }
+
         private void add(Object target, KeyframeSet keyframeSet) {
             mTargets.add(target);
             mValues.add(keyframeSet);
@@ -152,6 +182,9 @@
                         return View.SCALE_Y;
                 }
             }
+            if (target instanceof TouchAnimator && "position".equals(property)) {
+                return POSITION;
+            }
             return Property.of(target.getClass(), cls, property);
         }
 
@@ -208,6 +241,10 @@
         public static KeyframeSet ofFloat(Property property, float... values) {
             return new FloatKeyframeSet((Property<?, Float>) property, values);
         }
+
+        public static KeyframeSet ofPath(Property xProp, Property yProp, Path path) {
+            return new PathKeyframeSet<>(xProp, yProp, path);
+        }
     }
 
     private static class FloatKeyframeSet<T> extends KeyframeSet {
@@ -245,4 +282,31 @@
             mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount));
         }
     }
+
+    private static class PathKeyframeSet<T> extends KeyframeSet {
+        private final Property<T, Float> mXProp;
+        private final Property<T, Float> mYProp;
+        private final Path mPath;
+        private final PathMeasure mPathMeasure;
+        private final float mLength;
+        private final float[] mPos;
+
+        public PathKeyframeSet(Property<T, Float> xProp, Property<T, Float> yProp, Path path) {
+            super(2);
+            mXProp = xProp;
+            mYProp = yProp;
+            mPath = path;
+            mPathMeasure = new PathMeasure(mPath, false);
+            mLength = mPathMeasure.getLength();
+            mPos = new float[2];
+        }
+
+        @Override
+        protected void interpolate(int index, float amount, Object target) {
+            Pair<Object, Object> targets = (Pair<Object, Object>) target;
+            mPathMeasure.getPosTan(amount * mLength, mPos, null);
+            mXProp.set((T) targets.first, mPos[0]);
+            mYProp.set((T) targets.second, mPos[1]);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 0709992..db686a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -58,6 +58,7 @@
     private final IBinder mToken = new Binder();
     private final IQSTileService mService;
     private final TileServiceManager mServiceManager;
+    private final int mUser;
 
     private boolean mListening;
     private boolean mBound;
@@ -71,6 +72,7 @@
         mServiceManager = host.getTileServices().getTileWrapper(this);
         mService = mServiceManager.getTileService();
         mTile = new Tile(mComponent);
+        mUser = ActivityManager.getCurrentUser();
         try {
             PackageManager pm = mContext.getPackageManager();
             ServiceInfo info = pm.getServiceInfo(mComponent, 0);
@@ -86,6 +88,10 @@
         }
     }
 
+    public int getUser() {
+        return mUser;
+    }
+
     public ComponentName getComponent() {
         return mComponent;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index c4436f4..2aad161 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -85,6 +85,7 @@
         mHandler = handler;
         mIntent = intent;
         mUser = user;
+        if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
     }
 
     public ComponentName getComponent() {
@@ -116,13 +117,13 @@
             if (!checkComponentState()) {
                 return;
             }
-            if (DEBUG) Log.d(TAG, "Binding service " + mIntent);
+            if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
             mBindTryCount++;
             mContext.bindServiceAsUser(mIntent, this,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                     mUser);
         } else {
-            if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent);
+            if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
             // Give it another chance next time it needs to be bound, out of kindness.
             mBindTryCount = 0;
             mWrapper = null;
@@ -350,7 +351,7 @@
 
     @Override
     public void onClick(IBinder iBinder) {
-        if (DEBUG) Log.d(TAG, "onClick " + iBinder);
+        if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
         if (mWrapper == null || !mWrapper.onClick(iBinder)) {
             mClickBinder = iBinder;
             queueMessage(MSG_ON_CLICK);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index ac23f44..3ad2cc3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -645,7 +645,8 @@
         if (stack != null) {
             stackLayout.initialize(taskStackBounds,
                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
-            mDummyStackView.setTasks(stack, false /* notifyStackChanges */);
+            mDummyStackView.setTasks(stack, false /* notifyStackChanges */,
+                    false /* relayoutTaskStack */);
         }
         Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
         if (!taskViewBounds.equals(mLastTaskViewBounds)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 1af5a55..8342de5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -184,7 +184,8 @@
 
         // Update the stack
         mTaskStackView.onResume(isResumingFromVisible);
-        mTaskStackView.setTasks(stack, isResumingFromVisible /* notifyStackChanges */);
+        mTaskStackView.setTasks(stack, isResumingFromVisible /* notifyStackChanges */,
+                true /* relayoutTaskStack */);
 
         if (isResumingFromVisible) {
             // If we are already visible, then restore the background scrim
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 9da8fee..2601cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -298,7 +298,7 @@
     /**
      * Sets the stack tasks of this TaskStackView from the given TaskStack.
      */
-    public void setTasks(TaskStack stack, boolean notifyStackChanges) {
+    public void setTasks(TaskStack stack, boolean notifyStackChanges, boolean relayoutTaskStack) {
         boolean isInitialized = mLayoutAlgorithm.isInitialized();
         mStack.setTasks(getContext(), stack.computeAllTasksList(),
                 notifyStackChanges && isInitialized);
@@ -307,15 +307,18 @@
             // measure/layout pass
             updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
             updateToInitialState();
-            relayoutTaskViews(AnimationProps.IMMEDIATE);
 
-            // Rebind all the task views.  This will not trigger new resources to be loaded unless
-            // they have actually changed
-            List<TaskView> taskViews = getTaskViews();
-            int taskViewCount = taskViews.size();
-            for (int i = 0; i < taskViewCount; i++) {
-                TaskView tv = taskViews.get(i);
-                bindTaskView(tv, tv.getTask());
+            if (relayoutTaskStack) {
+                relayoutTaskViews(AnimationProps.IMMEDIATE);
+
+                // Rebind all the task views.  This will not trigger new resources to be loaded
+                // unless they have actually changed
+                List<TaskView> taskViews = getTaskViews();
+                int taskViewCount = taskViews.size();
+                for (int i = 0; i < taskViewCount; i++) {
+                    TaskView tv = taskViews.get(i);
+                    bindTaskView(tv, tv.getTask());
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index a06700d..e64354c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -39,6 +39,7 @@
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -407,6 +408,7 @@
 
     private Bitmap mScreenBitmap;
     private View mScreenshotLayout;
+    private ScreenshotSelectorView mScreenshotSelectorView;
     private ImageView mBackgroundView;
     private ImageView mScreenshotView;
     private ImageView mScreenshotFlash;
@@ -437,7 +439,11 @@
         mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
         mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
         mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
+        mScreenshotSelectorView = (ScreenshotSelectorView) mScreenshotLayout.findViewById(
+                R.id.global_screenshot_selector);
         mScreenshotLayout.setFocusable(true);
+        mScreenshotSelectorView.setFocusable(true);
+        mScreenshotSelectorView.setFocusableInTouchMode(true);
         mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
             @Override
             public boolean onTouch(View v, MotionEvent event) {
@@ -449,7 +455,7 @@
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
-                WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
+                WindowManager.LayoutParams.TYPE_SCREENSHOT,
                 WindowManager.LayoutParams.FLAG_FULLSCREEN
                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                     | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
@@ -525,7 +531,8 @@
     /**
      * Takes a screenshot of the current display and shows an animation.
      */
-    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
+    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
+            int x, int y, int width, int height) {
         // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
         // only in the natural orientation of the device :!)
         mDisplay.getRealMetrics(mDisplayMetrics);
@@ -565,6 +572,13 @@
             mScreenBitmap = ss;
         }
 
+        if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) {
+            // Crop the screenshot to selected region
+            Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height);
+            mScreenBitmap.recycle();
+            mScreenBitmap = cropped;
+        }
+
         // Optimizations
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
@@ -574,6 +588,71 @@
                 statusBarVisible, navBarVisible);
     }
 
+    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
+        mDisplay.getRealMetrics(mDisplayMetrics);
+        takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels,
+                mDisplayMetrics.heightPixels);
+    }
+
+    /**
+     * Displays a screenshot selector
+     */
+    void takeScreenshotPartial(final Runnable finisher, final boolean statusBarVisible,
+            final boolean navBarVisible) {
+        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        view.startSelection((int) event.getX(), (int) event.getY());
+                        return true;
+                    case MotionEvent.ACTION_MOVE:
+                        view.updateSelection((int) event.getX(), (int) event.getY());
+                        return true;
+                    case MotionEvent.ACTION_UP:
+                        view.setVisibility(View.GONE);
+                        mWindowManager.removeView(mScreenshotLayout);
+                        final Rect rect = view.getSelectionRect();
+                        if (rect != null) {
+                            if (rect.width() != 0 && rect.height() != 0) {
+                                // Need mScreenshotLayout to handle it after the view disappears
+                                mScreenshotLayout.post(new Runnable() {
+                                    public void run() {
+                                        takeScreenshot(finisher, statusBarVisible, navBarVisible,
+                                                rect.left, rect.top, rect.width(), rect.height());
+                                    }
+                                });
+                            }
+                        }
+
+                        view.stopSelection();
+                        return true;
+                }
+
+                return false;
+            }
+        });
+        mScreenshotLayout.post(new Runnable() {
+            @Override
+            public void run() {
+                mScreenshotSelectorView.setVisibility(View.VISIBLE);
+                mScreenshotSelectorView.requestFocus();
+            }
+        });
+    }
+
+    /**
+     * Cancels screenshot request
+     */
+    void stopScreenshot() {
+        // If the selector layer still presents on screen, we remove it and resets its state.
+        if (mScreenshotSelectorView.getSelectionRect() != null) {
+            mWindowManager.removeView(mScreenshotLayout);
+            mScreenshotSelectorView.stopSelection();
+        }
+    }
 
     /**
      * Starts the animation after taking the screenshot
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
new file mode 100644
index 0000000..07a9246
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
@@ -0,0 +1,82 @@
+/*
+ * 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.systemui.screenshot;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Draws a selection rectangle while taking screenshot
+ */
+public class ScreenshotSelectorView extends View {
+    private Point mStartPoint;
+    private Rect mSelectionRect;
+    private final Paint mPaintSelection, mPaintBackground;
+
+    public ScreenshotSelectorView(Context context) {
+        this(context, null);
+    }
+
+    public ScreenshotSelectorView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mPaintBackground = new Paint(Color.BLACK);
+        mPaintBackground.setAlpha(160);
+        mPaintSelection = new Paint(Color.TRANSPARENT);
+        mPaintSelection.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+    }
+
+    public void startSelection(int x, int y) {
+        mStartPoint = new Point(x, y);
+        mSelectionRect = new Rect(x, y, x, y);
+    }
+
+    public void updateSelection(int x, int y) {
+        if (mSelectionRect != null) {
+            mSelectionRect.left = Math.min(mStartPoint.x, x);
+            mSelectionRect.right = Math.max(mStartPoint.x, x);
+            mSelectionRect.top = Math.min(mStartPoint.y, y);
+            mSelectionRect.bottom = Math.max(mStartPoint.y, y);
+            invalidate();
+        }
+    }
+
+    public Rect getSelectionRect() {
+        return mSelectionRect;
+    }
+
+    public void stopSelection() {
+        mStartPoint = null;
+        mSelectionRect = null;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        canvas.drawRect(mLeft, mTop, mRight, mBottom, mPaintBackground);
+        if (mSelectionRect != null) {
+            canvas.drawRect(mSelectionRect, mPaintSelection);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 456b5fa..4badc42 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -23,6 +23,7 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.view.WindowManager;
 
 public class TakeScreenshotService extends Service {
     private static final String TAG = "TakeScreenshotService";
@@ -32,21 +33,28 @@
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case 1:
-                    final Messenger callback = msg.replyTo;
-                    if (mScreenshot == null) {
-                        mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
+            final Messenger callback = msg.replyTo;
+            Runnable finisher = new Runnable() {
+                @Override
+                public void run() {
+                    Message reply = Message.obtain(null, 1);
+                    try {
+                        callback.send(reply);
+                    } catch (RemoteException e) {
                     }
-                    mScreenshot.takeScreenshot(new Runnable() {
-                        @Override public void run() {
-                            Message reply = Message.obtain(null, 1);
-                            try {
-                                callback.send(reply);
-                            } catch (RemoteException e) {
-                            }
-                        }
-                    }, msg.arg1 > 0, msg.arg2 > 0);
+                }
+            };
+            if (mScreenshot == null) {
+                mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
+            }
+
+            switch (msg.what) {
+                case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
+                    mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    break;
+                case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
+                    mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    break;
             }
         }
     };
@@ -55,4 +63,10 @@
     public IBinder onBind(Intent intent) {
         return new Messenger(mHandler).getBinder();
     }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (mScreenshot != null) mScreenshot.stopScreenshot();
+        return true;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
index 7e7fc3a..c0148c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
@@ -41,4 +41,20 @@
      * Status bar is locked and shows the full screen user switcher.
      */
     public static final int FULLSCREEN_USER_SWITCHER = 3;
+
+
+    public static String toShortString(int x) {
+        switch (x) {
+            case SHADE:
+                return "SHD";
+            case SHADE_LOCKED:
+                return "SHD_LCK";
+            case KEYGUARD:
+                return "KGRD";
+            case FULLSCREEN_USER_SWITCHER:
+                return "FS_USRSW";
+            default:
+                return "bad_value_" + x;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 9c6f2c5..d50e67a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -92,6 +92,7 @@
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
     protected QSContainer mQsContainer;
+    private DensityContainer mQsDensityContainer;
     private KeyguardStatusView mKeyguardStatusView;
     private TextView mClockView;
     private View mReserveNotificationSpace;
@@ -217,8 +218,8 @@
         super.onFinishInflate();
         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
-        DensityContainer container = (DensityContainer) findViewById(R.id.qs_density_container);
-        container.addInflateListener(new InflateListener() {
+        mQsDensityContainer = (DensityContainer) findViewById(R.id.qs_density_container);
+        mQsDensityContainer.addInflateListener(new InflateListener() {
             @Override
             public void onInflated(View v) {
                 mQsContainer = (QSContainer) v.findViewById(R.id.quick_settings_container);
@@ -2209,7 +2210,7 @@
 
     protected void setVerticalPanelTranslation(float translation) {
         mNotificationStackScroller.setTranslationX(translation);
-        mQsContainer.setTranslationX(translation);
+        mQsDensityContainer.setTranslationX(translation);
     }
 
     protected void updateStackHeight(float stackHeight) {
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 7856f6c..6ad7aad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -112,6 +112,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
@@ -2263,6 +2264,10 @@
         mStatusBarWindowManager.setPanelExpanded(isExpanded);
     }
 
+    public void onScreenTurnedOff() {
+        mFalsingManager.onScreenOff();
+    }
+
     /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
@@ -2966,18 +2971,15 @@
             KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
         }
 
+        FalsingManager.getInstance(mContext).dump(pw);
+        FalsingLog.dump(pw);
+
         pw.println("SharedPreferences:");
         for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
             pw.print("  "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
         }
     }
 
-    private String hunStateToString(Entry entry) {
-        if (entry == null) return "null";
-        if (entry.notification == null) return "corrupt";
-        return entry.notification.getPackageName();
-    }
-
     private static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) {
         pw.print("  "); pw.print(var); pw.print(".BarTransitions.mMode=");
         pw.println(BarTransitions.modeToString(transitions.getMode()));
@@ -4186,7 +4188,6 @@
         mDeviceInteractive = false;
         mWakeUpComingFromTouch = false;
         mWakeUpTouchLocation = null;
-        mFalsingManager.onScreenOff();
         mStackScroller.setAnimationsEnabled(false);
         updateVisibleToUser();
         if (mLaunchCameraOnFinishedGoingToSleep) {
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 f894a22..5dcd393 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -114,6 +114,7 @@
     private final ManagedProfileController mProfileController;
     private final NextAlarmController mNextAlarmController;
     private View mHeader;
+    private int mCurrentUser;
 
     public QSTileHost(Context context, PhoneStatusBar statusBar,
             BluetoothController bluetooth, LocationController location,
@@ -320,7 +321,8 @@
         }
         if (DEBUG) Log.d(TAG, "Recreating tiles");
         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
-        if (tileSpecs.equals(mTileSpecs)) return;
+        int currentUser = ActivityManager.getCurrentUser();
+        if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
             if (!tileSpecs.contains(tile.getKey())) {
                 if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
@@ -329,15 +331,16 @@
         }
         final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
         for (String tileSpec : tileSpecs) {
-            if (mTiles.containsKey(tileSpec)) {
-                QSTile<?> tile = mTiles.get(tileSpec);
+            QSTile<?> tile = mTiles.get(tileSpec);
+            if (tile != null && (!(tile instanceof CustomTile)
+                    || ((CustomTile) tile).getUser() == currentUser)) {
                 if (DEBUG) Log.d(TAG, "Adding " + tile);
                 tile.removeCallbacks();
                 newTiles.put(tileSpec, tile);
             } else {
                 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
                 try {
-                    QSTile<?> tile = createTile(tileSpec);
+                    tile = createTile(tileSpec);
                     if (tile != null && tile.isAvailable()) {
                         tile.setTileSpec(tileSpec);
                         newTiles.put(tileSpec, tile);
@@ -347,6 +350,7 @@
                 }
             }
         }
+        mCurrentUser = currentUser;
         mTileSpecs.clear();
         mTileSpecs.addAll(tileSpecs);
         mTiles.clear();
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 1a557e4..3eda320 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -45,7 +45,6 @@
     public static final long ANIMATION_DURATION = 220;
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
             = new PathInterpolator(0f, 0, 0.7f, 1f);
-
     private static final float SCRIM_BEHIND_ALPHA = 0.62f;
     private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
     private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
@@ -60,6 +59,10 @@
     private final UnlockMethodCache mUnlockMethodCache;
     private final View mHeadsUpScrim;
 
+    private float mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
+    private float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
+    private float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
+
     protected boolean mKeyguardShowing;
     private float mFraction;
 
@@ -86,6 +89,7 @@
     private boolean mForceHideScrims;
     private boolean mSkipFirstFrame;
     private boolean mDontAnimateBouncerChanges;
+    private boolean mKeyguardFadingOutInProgress;
 
     public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim) {
         mScrimBehind = scrimBehind;
@@ -101,6 +105,19 @@
         scheduleUpdate();
     }
 
+    public void setShowScrimBehind(boolean show) {
+        if (show) {
+            mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
+            mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
+            mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
+        } else {
+            mScrimBehindAlpha = 0;
+            mScrimBehindAlphaKeyguard = 0;
+            mScrimBehindAlphaUnlocking = 0;
+        }
+        scheduleUpdate();
+    }
+
     public void onTrackingStarted() {
         mExpanding = true;
         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
@@ -229,7 +246,7 @@
             fraction = (float) Math.pow(fraction, 0.8f);
             behindFraction = (float) Math.pow(behindFraction, 0.8f);
             setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
-            setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD);
+            setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard);
         } else if (mBouncerShowing) {
             setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
             setScrimBehindColor(0f);
@@ -237,8 +254,8 @@
             float fraction = Math.max(0, Math.min(mFraction, 1));
             setScrimInFrontColor(0f);
             setScrimBehindColor(fraction
-                    * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING)
-                    + SCRIM_BEHIND_ALPHA_UNLOCKING);
+                    * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
+                    + mScrimBehindAlphaUnlocking);
         }
     }
 
@@ -251,7 +268,7 @@
         } else {
             // woo, special effects
             final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
-            setScrimBehindColor(k * SCRIM_BEHIND_ALPHA);
+            setScrimBehindColor(k * mScrimBehindAlpha);
         }
     }
 
@@ -326,12 +343,16 @@
                 if (mOnAnimationFinished != null) {
                     mOnAnimationFinished.run();
                     mOnAnimationFinished = null;
+                    mKeyguardFadingOutInProgress = false;
                 }
                 scrim.setTag(TAG_KEY_ANIM, null);
                 scrim.setTag(TAG_KEY_ANIM_TARGET, null);
             }
         });
         anim.start();
+        if (mAnimateKeyguardFadingOut) {
+            mKeyguardFadingOutInProgress = true;
+        }
         if (mSkipFirstFrame) {
             anim.setCurrentPlayTime(16);
         }
@@ -366,6 +387,7 @@
                 && mOnAnimationFinished != null) {
             mOnAnimationFinished.run();
             mOnAnimationFinished = null;
+            mKeyguardFadingOutInProgress = false;
         }
     }
 
@@ -406,6 +428,10 @@
     }
 
     private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
+        if (mKeyguardFadingOutInProgress) {
+            return;
+        }
+
         ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim,
                 TAG_KEY_ANIM);
         float animEndValue = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0e84f733..2ba1562 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -212,6 +212,7 @@
 
     public void onScreenTurnedOff() {
         mScreenTurnedOn = false;
+        mPhoneStatusBar.onScreenTurnedOff();
     }
 
     public void notifyDeviceWakeUpRequested() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index cca3746..824b8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -3617,7 +3617,6 @@
 
                 // ANIMATION_TYPE_DIMMED
                 new AnimationFilter()
-                        .animateY()
                         .animateDimmed(),
 
                 // ANIMATION_TYPE_CHANGE_POSITION
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index 123165e..ea15b81 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -34,6 +34,7 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.systemui.Prefs;
@@ -51,7 +52,8 @@
 public class PipManager {
     private static final String TAG = "PipManager";
     private static final boolean DEBUG = false;
-    private static final boolean DEBUG_FORCE_ONBOARDING = false;
+    private static final boolean DEBUG_FORCE_ONBOARDING =
+            SystemProperties.getBoolean("debug.tv.pip_force_onboarding", false);
 
     private static PipManager sPipManager;
 
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 4877a378..4e667c6 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1062,7 +1062,7 @@
               type, kind, norm, size);
     }
     return (jlong)(uintptr_t)rsElementCreate((RsContext)con, (RsDataType)type, (RsDataKind)kind,
-                                             norm, size, true);
+                                             norm, size);
 }
 
 static jlong
@@ -1101,7 +1101,7 @@
     jlong id = (jlong)(uintptr_t)rsElementCreate2((RsContext)con,
                                      (const RsElement *)ids, fieldCount,
                                      nameArray, fieldCount * sizeof(size_t),  sizeArray,
-                                     (const uint32_t *)arraySizes, fieldCount, true);
+                                     (const uint32_t *)arraySizes, fieldCount);
 
     free(ids);
     free(arraySizes);
@@ -1175,7 +1175,7 @@
     }
 
     return (jlong)(uintptr_t)rsTypeCreate((RsContext)con, (RsElement)eid, dimx, dimy, dimz, mips,
-                                          faces, yuv, true);
+                                          faces, yuv);
 }
 
 static void
@@ -1211,7 +1211,7 @@
     }
     return (jlong)(uintptr_t) rsAllocationCreateTyped((RsContext)con, (RsType)type,
                                                       (RsAllocationMipmapControl)mips,
-                                                      (uint32_t)usage, (uintptr_t)pointer, true);
+                                                      (uint32_t)usage, (uintptr_t)pointer);
 }
 
 static void
@@ -1316,7 +1316,7 @@
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCreateFromBitmap((RsContext)con,
                                                   (RsType)type, (RsAllocationMipmapControl)mip,
-                                                  ptr, bitmap.getSize(), usage, true);
+                                                  ptr, bitmap.getSize(), usage);
     bitmap.unlockPixels();
     return id;
 }
@@ -1332,7 +1332,7 @@
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCreateTyped((RsContext)con,
                                             (RsType)type, (RsAllocationMipmapControl)mip,
-                                            (uint32_t)usage, (uintptr_t)ptr, true);
+                                            (uint32_t)usage, (uintptr_t)ptr);
     bitmap.unlockPixels();
     return id;
 }
@@ -1348,7 +1348,7 @@
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCubeCreateFromBitmap((RsContext)con,
                                                       (RsType)type, (RsAllocationMipmapControl)mip,
-                                                      ptr, bitmap.getSize(), usage, true);
+                                                      ptr, bitmap.getSize(), usage);
     bitmap.unlockPixels();
     return id;
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9e6c21c..b4b40ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3436,7 +3436,8 @@
                 case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
                 case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
                 case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
-                case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: {
+                case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
+                case WindowManager.LayoutParams.TYPE_SCREENSHOT: {
                     return AccessibilityWindowInfo.TYPE_SYSTEM;
                 }
 
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 3f453dc..306e933 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -27,6 +27,7 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
@@ -80,6 +81,8 @@
     private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<>();
     private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups = new HashMap<>();
     private final TextServicesSettings mSettings;
+    @NonNull
+    private final UserManager mUserManager;
 
     public static final class Lifecycle extends SystemService {
         private TextServicesManagerService mService;
@@ -109,19 +112,37 @@
                 mService.systemRunning();
             }
         }
+
+        @Override
+        public void onUnlockUser(@UserIdInt int userHandle) {
+            // Called on the system server's main looper thread.
+            // TODO: Dispatch this to a worker thread as needed.
+            mService.onUnlockUser(userHandle);
+        }
     }
 
     void systemRunning() {
         synchronized (mSpellCheckerMap) {
             if (!mSystemReady) {
                 mSystemReady = true;
+                resetInternalState(mSettings.getCurrentUserId());
             }
         }
     }
 
     void onSwitchUser(@UserIdInt int userId) {
         synchronized (mSpellCheckerMap) {
-            switchUserLocked(userId);
+            resetInternalState(userId);
+        }
+    }
+
+    void onUnlockUser(@UserIdInt int userId) {
+        synchronized(mSpellCheckerMap) {
+            final int currentUserId = mSettings.getCurrentUserId();
+            if (userId != currentUserId) {
+                return;
+            }
+            resetInternalState(currentUserId);
         }
     }
 
@@ -129,6 +150,8 @@
         mSystemReady = false;
         mContext = context;
 
+        mUserManager = mContext.getSystemService(UserManager.class);
+
         final IntentFilter broadcastFilter = new IntentFilter();
         broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
         broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
@@ -142,14 +165,19 @@
         }
         mMonitor = new TextServicesMonitor();
         mMonitor.register(context, null, true);
-        mSettings = new TextServicesSettings(context.getContentResolver(), userId);
+        final boolean useCopyOnWriteSettings =
+                !mSystemReady || !mUserManager.isUserUnlocked(userId);
+        mSettings = new TextServicesSettings(context.getContentResolver(), userId,
+                useCopyOnWriteSettings);
 
-        // "switchUserLocked" initializes the states for the foreground user
-        switchUserLocked(userId);
+        // "resetInternalState" initializes the states for the foreground user
+        resetInternalState(userId);
     }
 
-    private void switchUserLocked(@UserIdInt int userId) {
-        mSettings.setCurrentUserId(userId);
+    private void resetInternalState(@UserIdInt int userId) {
+        final boolean useCopyOnWriteSettings =
+                !mSystemReady || !mUserManager.isUserUnlocked(userId);
+        mSettings.switchCurrentUser(userId, useCopyOnWriteSettings);
         updateCurrentProfileIds();
         unbindServiceLocked();
         buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings);
@@ -166,8 +194,7 @@
     }
 
     void updateCurrentProfileIds() {
-        List<UserInfo> profiles =
-                UserManager.get(mContext).getProfiles(mSettings.getCurrentUserId());
+        final List<UserInfo> profiles = mUserManager.getProfiles(mSettings.getCurrentUserId());
         int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
         for (int i = 0; i < currentProfileIds.length; i++) {
             currentProfileIds[i] = profiles.get(i).id;
@@ -232,6 +259,9 @@
         list.clear();
         map.clear();
         final PackageManager pm = context.getPackageManager();
+        // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
+        // behavior of PackageManager is exactly what we want.  It by default picks up appropriate
+        // services depending on the unlock state for the specified user.
         final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                 new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
                 settings.getCurrentUserId());
@@ -774,50 +804,36 @@
 
         synchronized(mSpellCheckerMap) {
             pw.println("Current Text Services Manager state:");
-            pw.println("  Spell Checker Map:");
-            for (Map.Entry<String, SpellCheckerInfo> ent : mSpellCheckerMap.entrySet()) {
-                pw.print("    "); pw.print(ent.getKey()); pw.println(":");
-                SpellCheckerInfo info = ent.getValue();
-                pw.print("      "); pw.print("id="); pw.println(info.getId());
-                pw.print("      "); pw.print("comp=");
-                        pw.println(info.getComponent().toShortString());
-                int NS = info.getSubtypeCount();
-                for (int i=0; i<NS; i++) {
-                    SpellCheckerSubtype st = info.getSubtypeAt(i);
-                    pw.print("      "); pw.print("Subtype #"); pw.print(i); pw.println(":");
-                    pw.print("        "); pw.print("locale="); pw.println(st.getLocale());
-                    pw.print("        "); pw.print("extraValue=");
-                            pw.println(st.getExtraValue());
-                }
+            pw.println("  Spell Checkers:");
+            int spellCheckerIndex = 0;
+            for (final SpellCheckerInfo info : mSpellCheckerMap.values()) {
+                pw.println("  Spell Checker #" + spellCheckerIndex);
+                info.dump(pw, "    ");
+                ++spellCheckerIndex;
             }
             pw.println("");
             pw.println("  Spell Checker Bind Groups:");
-            for (Map.Entry<String, SpellCheckerBindGroup> ent
+            for (final Map.Entry<String, SpellCheckerBindGroup> ent
                     : mSpellCheckerBindGroups.entrySet()) {
-                SpellCheckerBindGroup grp = ent.getValue();
-                pw.print("    "); pw.print(ent.getKey()); pw.print(" ");
-                        pw.print(grp); pw.println(":");
-                pw.print("      "); pw.print("mInternalConnection=");
-                        pw.println(grp.mInternalConnection);
-                pw.print("      "); pw.print("mSpellChecker=");
-                        pw.println(grp.mSpellChecker);
-                pw.print("      "); pw.print("mBound="); pw.print(grp.mBound);
-                        pw.print(" mConnected="); pw.println(grp.mConnected);
-                int NL = grp.mListeners.size();
-                for (int i=0; i<NL; i++) {
-                    InternalDeathRecipient listener = grp.mListeners.get(i);
-                    pw.print("      "); pw.print("Listener #"); pw.print(i); pw.println(":");
-                    pw.print("        "); pw.print("mTsListener=");
-                            pw.println(listener.mTsListener);
-                    pw.print("        "); pw.print("mScListener=");
-                            pw.println(listener.mScListener);
-                    pw.print("        "); pw.print("mGroup=");
-                            pw.println(listener.mGroup);
-                    pw.print("        "); pw.print("mScLocale=");
-                            pw.print(listener.mScLocale);
-                            pw.print(" mUid="); pw.println(listener.mUid);
+                final SpellCheckerBindGroup grp = ent.getValue();
+                pw.println("    " + ent.getKey() + " " + grp + ":");
+                pw.println("      " + "mInternalConnection=" + grp.mInternalConnection);
+                pw.println("      " + "mSpellChecker=" + grp.mSpellChecker);
+                pw.println("      " + "mBound=" + grp.mBound + " mConnected=" + grp.mConnected);
+                final int N = grp.mListeners.size();
+                for (int i = 0; i < N; i++) {
+                    final InternalDeathRecipient listener = grp.mListeners.get(i);
+                    pw.println("      " + "Listener #" + i + ":");
+                    pw.println("        " + "mTsListener=" + listener.mTsListener);
+                    pw.println("        " + "mScListener=" + listener.mScListener);
+                    pw.println("        " + "mGroup=" + listener.mGroup);
+                    pw.println("        " + "mScLocale=" + listener.mScLocale
+                            + " mUid=" + listener.mUid);
                 }
             }
+            pw.println("");
+            pw.println("  mSettings:");
+            mSettings.dumpLocked(pw, "    ");
         }
     }
 
@@ -1037,33 +1053,70 @@
         private int[] mCurrentProfileIds = new int[0];
         private Object mLock = new Object();
 
-        public TextServicesSettings(ContentResolver resolver, @UserIdInt int userId) {
+        /**
+         * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
+         */
+        private final HashMap<String, String> mCopyOnWriteDataStore = new HashMap<>();
+        private boolean mCopyOnWrite = false;
+
+        public TextServicesSettings(ContentResolver resolver, @UserIdInt int userId,
+                boolean copyOnWrite) {
             mResolver = resolver;
-            mCurrentUserId = userId;
+            switchCurrentUser(userId, copyOnWrite);
         }
 
-        public void setCurrentUserId(@UserIdInt int userId) {
+        /**
+         * Must be called when the current user is changed.
+         *
+         * @param userId The user ID.
+         * @param copyOnWrite If {@code true}, for each settings key
+         * (e.g. {@link Settings.Secure#SELECTED_SPELL_CHECKER}) we use the actual settings on the
+         * {@link Settings.Secure} until we do the first write operation.
+         */
+        public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
             if (DBG) {
                 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
                         + userId + ", new ime = " + getSelectedSpellChecker());
             }
+            if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
+                mCopyOnWriteDataStore.clear();
+                // TODO: mCurrentProfileIds should be cleared here.
+            }
             // TSMS settings are kept per user, so keep track of current user
             mCurrentUserId = userId;
+            mCopyOnWrite = copyOnWrite;
+            // TODO: mCurrentProfileIds should be updated here.
         }
 
         private void putString(final String key, final String str) {
-            Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
+            if (mCopyOnWrite) {
+                mCopyOnWriteDataStore.put(key, str);
+            } else {
+                Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
+            }
         }
 
         private String getString(final String key) {
+            if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
+                final String result = mCopyOnWriteDataStore.get(key);
+                return result != null ? result : "";
+            }
             return Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
         }
 
         private void putInt(final String key, final int value) {
-            Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
+            if (mCopyOnWrite) {
+                mCopyOnWriteDataStore.put(key, String.valueOf(value));
+            } else {
+                Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
+            }
         }
 
         private int getInt(final String key, final int defaultValue) {
+            if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
+                final String result = mCopyOnWriteDataStore.get(key);
+                return result != null ? Integer.valueOf(result) : 0;
+            }
             return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
         }
 
@@ -1119,6 +1172,12 @@
         public boolean isSpellCheckerEnabled() {
             return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true);
         }
+
+        public void dumpLocked(final PrintWriter pw, final String prefix) {
+            pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
+            pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
+            pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
+        }
     }
 
     // ----------------------------------------------------------------------
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0317641..f07c1d0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1899,7 +1899,9 @@
             case SYSTEM_USER_UNLOCK_MSG: {
                 final int userId = msg.arg1;
                 mSystemServiceManager.unlockUser(userId);
-                mRecentTasks.loadUserRecentsLocked(userId);
+                synchronized (ActivityManagerService.this) {
+                    mRecentTasks.loadUserRecentsLocked(userId);
+                }
                 if (userId == UserHandle.USER_SYSTEM) {
                     startPersistentApps(PackageManager.MATCH_ENCRYPTION_UNAWARE);
                 }
@@ -6655,7 +6657,7 @@
         synchronized(this) {
             ActivityStack stack = ActivityRecord.getStackLocked(token);
             if (stack != null) {
-                ActivityRecord.activityResumedLocked(token);
+                stack.activityResumedLocked(token);
             }
         }
         Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2ea5d15..5219827 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1277,13 +1277,6 @@
         }
     }
 
-    static void activityResumedLocked(IBinder token) {
-        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-        if (DEBUG_SAVED_STATE) Slog.i(TAG_STATES, "Resumed activity; dropping state of: " + r);
-        r.icicle = null;
-        r.haveState = false;
-    }
-
     static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         if (r == null) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e50722a..39895ac5 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1088,6 +1088,13 @@
         mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
     }
 
+    final void activityResumedLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+        if (DEBUG_SAVED_STATE) Slog.i(TAG_STATES, "Resumed activity; dropping state of: " + r);
+        r.icicle = null;
+        r.haveState = false;
+    }
+
     final void activityStoppedLocked(ActivityRecord r, Bundle icicle,
             PersistableBundle persistentState, CharSequence description) {
         if (r.state != ActivityState.STOPPING) {
@@ -4436,10 +4443,10 @@
                     "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r
                     + " callers=" + Debug.getCallers(6));
             r.forceNewConfig = false;
+            mStackSupervisor.activityRelaunchingLocked(r);
             r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
                     !andResume, new Configuration(mService.mConfiguration),
                     new Configuration(r.task.mOverrideConfig), preserveWindow);
-            mStackSupervisor.activityRelaunchingLocked(r);
             // Note: don't need to call pauseIfSleepingLocked() here, because
             // the caller will only pass in 'andResume' if this activity is
             // currently resumed, which implies we aren't sleeping.
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 62275a9..ea85fa1 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -40,6 +40,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Slog;
@@ -74,6 +75,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
@@ -444,9 +446,9 @@
             // task as having a true root activity.
             rootWasReset = true;
         }
-
         userId = UserHandle.getUserId(info.applicationInfo.uid);
-        mUserSetupComplete = mService.mUserController.isUserSetupCompleteLocked(userId);
+        mUserSetupComplete = Settings.Secure.getIntForUser(mService.mContext.getContentResolver(),
+                USER_SETUP_COMPLETE, 0, userId) != 0;
         if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
             // If the activity itself has requested auto-remove, then just always do it.
             autoRemoveRecents = true;
@@ -1570,6 +1572,7 @@
         pw.print(prefix); pw.print("userId="); pw.print(userId);
                 pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
                 pw.print(" mCallingUid="); UserHandle.formatUid(pw, mCallingUid);
+                pw.print(" mUserSetupComplete="); pw.print(mUserSetupComplete);
                 pw.print(" mCallingPackage="); pw.println(mCallingPackage);
         if (affinity != null || rootAffinity != null) {
             pw.print(prefix); pw.print("affinity="); pw.print(affinity);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index addffd3..4a5df7a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -24,7 +24,6 @@
 import static android.app.ActivityManager.USER_OP_SUCCESS;
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.os.Process.SYSTEM_UID;
-import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -46,14 +45,11 @@
 import android.app.IStopUserCallback;
 import android.app.IUserSwitchObserver;
 import android.app.KeyguardManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Bundle;
@@ -71,12 +67,10 @@
 import android.os.UserManagerInternal;
 import android.os.storage.IMountService;
 import android.os.storage.StorageManager;
-import android.provider.Settings;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
@@ -153,34 +147,6 @@
 
     private final LockPatternUtils mLockPatternUtils;
 
-    // Set of users who have completed the set-up process.
-    private final SparseBooleanArray mSetupCompletedUsers = new SparseBooleanArray();
-    private final UserSetupCompleteContentObserver mUserSetupCompleteContentObserver;
-
-    private class UserSetupCompleteContentObserver extends ContentObserver {
-        private final Uri mUserSetupComplete = Settings.Secure.getUriFor(USER_SETUP_COMPLETE);
-
-        public UserSetupCompleteContentObserver(Handler handler) {
-            super(handler);
-        }
-
-        void register(ContentResolver resolver) {
-            resolver.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL);
-            synchronized (mService) {
-                updateUserSetupCompleteLocked(UserHandle.USER_ALL);
-            }
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri, int userId) {
-            if (mUserSetupComplete.equals(uri)) {
-                synchronized (mService) {
-                    updateUserSetupCompleteLocked(userId);
-                }
-            }
-        }
-    }
-
     UserController(ActivityManagerService service) {
         mService = service;
         mHandler = mService.mHandler;
@@ -190,7 +156,6 @@
         mUserLru.add(UserHandle.USER_SYSTEM);
         mLockPatternUtils = new LockPatternUtils(mService.mContext);
         updateStartedUserArrayLocked();
-        mUserSetupCompleteContentObserver = new UserSetupCompleteContentObserver(mHandler);
     }
 
     void finishUserSwitch(UserState uss) {
@@ -477,7 +442,6 @@
                 mStartedUsers.remove(userId);
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLocked();
-                mSetupCompletedUsers.delete(userId);
 
                 mService.onUserStoppedLocked(userId);
                 // Clean up all state and processes associated with the user.
@@ -677,7 +641,6 @@
                 final Integer userIdInt = userId;
                 mUserLru.remove(userIdInt);
                 mUserLru.add(userIdInt);
-                updateUserSetupCompleteLocked(userId);
 
                 if (foreground) {
                     mCurrentUserId = userId;
@@ -892,22 +855,6 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    void updateUserSetupCompleteLocked(int userId) {
-        final ContentResolver cr = mService.mContext.getContentResolver();
-        for (int i = mStartedUsers.size() - 1; i >= 0; i--) {
-            int startedUser = mStartedUsers.keyAt(i);
-            if (startedUser == userId || userId == UserHandle.USER_ALL) {
-                final boolean setupComplete =
-                        Settings.Secure.getIntForUser(cr, USER_SETUP_COMPLETE, 0, startedUser) != 0;
-                mSetupCompletedUsers.put(startedUser, setupComplete);
-            }
-        }
-    }
-
-    boolean isUserSetupCompleteLocked(int userId) {
-        return mSetupCompletedUsers.get(userId);
-    }
-
     private void stopBackgroundUsersIfEnforced(int oldUserId) {
         // Never stop system user
         if (oldUserId == UserHandle.USER_SYSTEM) {
@@ -1218,7 +1165,6 @@
 
     void onSystemReady() {
         updateCurrentProfileIdsLocked();
-        mUserSetupCompleteContentObserver.register(mService.mContext.getContentResolver());
     }
 
     /**
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index fba7e7d..d2b77aa 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -47,14 +47,15 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Slog;
 import android.util.SparseArray;
-
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.app.ProcessStats;
@@ -1337,8 +1338,48 @@
                 Binder.restoreCallingIdentity(identityToken);
             }
         }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ResultReceiver resultReceiver) throws RemoteException {
+                (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
+                        this, in, out, err, args, resultReceiver);
+        }
     };
 
+    // Shell command infrastructure: run the given job immediately
+    int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
+        if (DEBUG) {
+            Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
+                    + " " + jobId + " f=" + force);
+        }
+
+        try {
+            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
+            if (uid < 0) {
+                return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+            }
+
+            synchronized (mLock) {
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+                if (js == null) {
+                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
+                }
+
+                js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
+                if (!js.isConstraintsSatisfied()) {
+                    js.overrideState = 0;
+                    return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
+                }
+
+                mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
+            }
+        } catch (RemoteException e) {
+            // can't happen
+        }
+        return 0;
+    }
+
     private String printContextIdToJobMap(JobStatus[] map, String initial) {
         StringBuilder s = new StringBuilder(initial + ": ");
         for (int i=0; i<map.length; i++) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
new file mode 100644
index 0000000..2d62c1c
--- /dev/null
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -0,0 +1,154 @@
+/*
+ * 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.job;
+
+import android.app.AppGlobals;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+public class JobSchedulerShellCommand extends ShellCommand {
+    public static final int CMD_ERR_NO_PACKAGE = -1000;
+    public static final int CMD_ERR_NO_JOB = -1001;
+    public static final int CMD_ERR_CONSTRAINTS = -1002;
+
+    JobSchedulerService mInternal;
+    IPackageManager mPM;
+
+    JobSchedulerShellCommand(JobSchedulerService service) {
+        mInternal = service;
+        mPM = AppGlobals.getPackageManager();
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            if ("run".equals(cmd)) {
+                return runJob();
+            } else {
+                return handleDefaultCommands(cmd);
+            }
+        } catch (Exception e) {
+            pw.println("Exception: " + e);
+        }
+        return -1;
+    }
+
+    private int runJob() {
+        try {
+            final int uid = Binder.getCallingUid();
+            final int perm = mPM.checkUidPermission(
+                    "android.permission.CHANGE_APP_IDLE_STATE", uid);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Uid " + uid
+                        + " not permitted to force scheduled jobs");
+            }
+        } catch (RemoteException e) {
+            // Can't happen
+        }
+
+        final PrintWriter pw = getOutPrintWriter();
+        boolean force = false;
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-f":
+                case "--force":
+                    force = true;
+                    break;
+
+                case "-u":
+                case "--user":
+                    userId = Integer.parseInt(getNextArgRequired());
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        final String pkgName = getNextArgRequired();
+        final int jobId = Integer.parseInt(getNextArgRequired());
+
+        int ret = mInternal.executeRunCommand(pkgName, userId, jobId, force);
+        switch (ret) {
+            case CMD_ERR_NO_PACKAGE:
+                pw.print("Package not found: ");
+                pw.print(pkgName);
+                pw.print(" / user ");
+                pw.println(userId);
+                break;
+
+            case CMD_ERR_NO_JOB:
+                pw.print("Could not find job ");
+                pw.print(jobId);
+                pw.print(" in package ");
+                pw.print(pkgName);
+                pw.print(" / user ");
+                pw.println(userId);
+                break;
+
+            case CMD_ERR_CONSTRAINTS:
+                pw.print("Job ");
+                pw.print(jobId);
+                pw.print(" in package ");
+                pw.print(pkgName);
+                pw.print(" / user ");
+                pw.print(userId);
+                pw.println(" has functional constraints but --force not specified");
+                break;
+
+            default:
+                // success!
+                pw.print("Running job");
+                if (force) {
+                    pw.print(" [FORCED]");
+                }
+                pw.println();
+                break;
+        }
+        return ret;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+
+        pw.println("Job scheduler (jobscheduler) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  run [-f | --force] [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("    Trigger immediate execution of a specific scheduled job.");
+        pw.println("    Options:");
+        pw.println("      -f or --force: run the job even if technical constraints such as");
+        pw.println("         connectivity are not currently met");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
+        pw.println();
+    }
+
+}
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 98bf8a9..4a2c88c 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -55,6 +55,11 @@
     static final int CONSTRAINT_APP_NOT_IDLE = 1<<6;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<7;
 
+    // Soft override: ignore constraints like time that don't affect API availability
+    public static final int OVERRIDE_SOFT = 1;
+    // Full override: ignore all constraints including API-affecting like connectivity
+    public static final int OVERRIDE_FULL = 2;
+
     final JobInfo job;
     /** Uid of the package requesting this job. */
     final int callingUid;
@@ -91,6 +96,9 @@
 
     public int lastEvaluatedPriority;
 
+    // Used by shell commands
+    public int overrideState = 0;
+
     /**
      * For use only by ContentObserverController: state it is maintaining about content URIs
      * being observed.
@@ -370,7 +378,7 @@
      */
     public boolean isReady() {
         // Deadline constraint trumps other constraints (except for periodic jobs where deadline
-        // (is an implementation detail. A periodic job should only run if it's constraints are
+        // is an implementation detail. A periodic job should only run if its constraints are
         // satisfied).
         // AppNotIdle implicit constraint trumps all!
         return (isConstraintsSatisfied()
@@ -384,12 +392,27 @@
             CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
             CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
 
+    // Soft override covers all non-"functional" constraints
+    static final int SOFT_OVERRIDE_CONSTRAINTS =
+            CONSTRAINT_CHARGING | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
+
     /**
      * @return Whether the constraints set on this job are satisfied.
      */
     public boolean isConstraintsSatisfied() {
+        if (overrideState == OVERRIDE_FULL) {
+            // force override: the job is always runnable
+            return true;
+        }
+
         final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST;
-        final int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
+
+        int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
+        if (overrideState == OVERRIDE_SOFT) {
+            // override: pretend all 'soft' requirements are satisfied
+            sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
+        }
+
         return (sat & req) == req;
     }
 
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 9820a12..c19b51f 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import android.app.AutomaticZenRule;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -44,7 +45,6 @@
 
 public class ConditionProviders extends ManagedServices {
     private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
-    private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>();
     private final ArraySet<String> mSystemConditionProviderNames;
     private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
             = new ArraySet<>();
@@ -103,12 +103,6 @@
                 }
             }
         }
-        if (filter == null) {
-            pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
-            for (int i = 0; i < mListeners.size(); i++) {
-                pw.print("      "); pw.println(mListeners.keyAt(i));
-            }
-        }
         pw.print("    mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
         for (int i = 0; i < mSystemConditionProviders.size(); i++) {
             mSystemConditionProviders.valueAt(i).dump(pw, filter);
@@ -173,16 +167,12 @@
         }
     }
 
-    private Condition[] validateConditions(String pkg, Condition[] conditions) {
+    private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
         if (conditions == null || conditions.length == 0) return null;
         final int N = conditions.length;
         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
         for (int i = 0; i < N; i++) {
             final Uri id = conditions[i].id;
-            if (!Condition.isValidId(id, pkg)) {
-                Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
-                continue;
-            }
             if (valid.containsKey(id)) {
                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
                 continue;
@@ -219,16 +209,9 @@
         synchronized(mMutex) {
             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
                     + (conditions == null ? null : Arrays.asList(conditions)));
-            conditions = validateConditions(pkg, conditions);
+            conditions = removeDuplicateConditions(pkg, conditions);
             if (conditions == null || conditions.length == 0) return;
             final int N = conditions.length;
-            for (IConditionListener listener : mListeners.values()) {
-                try {
-                    listener.onConditionsReceived(conditions);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Error sending conditions to listener " + listener, e);
-                }
-            }
             for (int i = 0; i < N; i++) {
                 final Condition c = conditions[i];
                 final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8d75f60..5612870 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.pm;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.Context;
@@ -27,8 +30,12 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 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.pm.UserInfo;
 import android.graphics.Rect;
 import android.net.Uri;
@@ -44,15 +51,16 @@
 import android.util.Slog;
 
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import java.util.List;
 
 /**
  * Service that manages requests and callbacks for launchers that support
- * managed profiles. 
+ * managed profiles.
  */
-
 public class LauncherAppsService extends SystemService {
 
     private final LauncherAppsImpl mLauncherAppsImpl;
@@ -67,21 +75,25 @@
         publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl);
     }
 
-    class LauncherAppsImpl extends ILauncherApps.Stub {
+    static class LauncherAppsImpl extends ILauncherApps.Stub {
         private static final boolean DEBUG = false;
         private static final String TAG = "LauncherAppsService";
         private final Context mContext;
         private final PackageManager mPm;
         private final UserManager mUm;
+        private final ShortcutServiceInternal mShortcutServiceInternal;
         private final PackageCallbackList<IOnAppsChangedListener> mListeners
                 = new PackageCallbackList<IOnAppsChangedListener>();
 
-        private MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+        private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
 
         public LauncherAppsImpl(Context context) {
             mContext = context;
             mPm = mContext.getPackageManager();
             mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+            mShortcutServiceInternal = Preconditions.checkNotNull(
+                    LocalServices.getService(ShortcutServiceInternal.class));
+            mShortcutServiceInternal.addListener(mPackageMonitor);
         }
 
         /*
@@ -174,6 +186,20 @@
             }
         }
 
+        private void verifyCallingPackage(String callingPackage) {
+            int packageUid = -1;
+            try {
+                packageUid = mPm.getPackageUid(callingPackage,
+                        PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE
+                                | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+            } catch (NameNotFoundException e) {
+                Log.e(TAG, "Package not found: " + callingPackage);
+            }
+            if (packageUid != Binder.getCallingUid()) {
+                throw new SecurityException("Calling package name mismatch");
+            }
+        }
+
         /**
          * Checks if the user is enabled.
          */
@@ -264,6 +290,70 @@
             }
         }
 
+        private void enforceShortcutPermission(UserHandle user) {
+            ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
+            // STOPSHIP Implement it
+        }
+
+        @Override
+        public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
+                String packageName, ComponentName componentName, int flags, UserHandle user)
+                throws RemoteException {
+            enforceShortcutPermission(user);
+            verifyCallingPackage(callingPackage);
+
+            return new ParceledListSlice<>(
+                    mShortcutServiceInternal.getShortcuts(callingPackage, changedSince, packageName,
+                    componentName, flags, user.getIdentifier()));
+        }
+
+        @Override
+        public ParceledListSlice getShortcutInfo(String callingPackage, String packageName,
+                List<String> ids, UserHandle user) throws RemoteException {
+            enforceShortcutPermission(user);
+            verifyCallingPackage(callingPackage);
+
+            return new ParceledListSlice<>(
+                    mShortcutServiceInternal.getShortcutInfo(callingPackage, packageName,
+                    ids, user.getIdentifier()));
+        }
+
+        @Override
+        public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
+                UserHandle user) throws RemoteException {
+            enforceShortcutPermission(user);
+            verifyCallingPackage(callingPackage);
+
+            mShortcutServiceInternal.pinShortcuts(callingPackage, packageName,
+                    ids, user.getIdentifier());
+        }
+
+        @Override
+        public boolean startShortcut(String callingPackage, String packageName, String shortcutId,
+                Rect sourceBounds, Bundle startActivityOptions, UserHandle user)
+                throws RemoteException {
+            enforceShortcutPermission(user);
+            verifyCallingPackage(callingPackage);
+
+            final Intent intent = mShortcutServiceInternal.createShortcutIntent(callingPackage,
+                    packageName, shortcutId, user.getIdentifier());
+            if (intent == null) {
+                return false;
+            }
+            // Note the target activity doesn't have to be exported.
+
+            intent.setSourceBounds(sourceBounds);
+            prepareIntentForLaunch(intent, sourceBounds);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mContext.startActivityAsUser(intent, startActivityOptions, user);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            return true;
+        }
+
         @Override
         public boolean isActivityEnabled(ComponentName component, UserHandle user)
                 throws RemoteException {
@@ -293,9 +383,7 @@
 
             Intent launchIntent = new Intent(Intent.ACTION_MAIN);
             launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            launchIntent.setSourceBounds(sourceBounds);
-            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+            prepareIntentForLaunch(launchIntent, sourceBounds);
             launchIntent.setPackage(component.getPackageName());
 
             long ident = Binder.clearCallingIdentity();
@@ -332,6 +420,13 @@
             }
         }
 
+        private void prepareIntentForLaunch(@NonNull Intent launchIntent,
+                @Nullable Rect sourceBounds) {
+            launchIntent.setSourceBounds(sourceBounds);
+            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        }
+
         @Override
         public void showAppDetailsAsUser(ComponentName component, Rect sourceBounds,
                 Bundle opts, UserHandle user) throws RemoteException {
@@ -355,7 +450,7 @@
         }
 
 
-        private class MyPackageMonitor extends PackageMonitor {
+        private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener {
 
             /** Checks if user is a profile of or same as listeningUser.
               * and the user is enabled. */
@@ -390,6 +485,8 @@
                 }
             }
 
+            // TODO Simplify with lambdas.
+
             @Override
             public void onPackageAdded(String packageName, int uid) {
                 UserHandle user = new UserHandle(getChangingUserId());
@@ -523,6 +620,25 @@
                 super.onPackagesUnsuspended(packages);
             }
 
+            @Override
+            public void onShortcutChanged(@NonNull String packageName,
+                    @NonNull List<ShortcutInfo> shortcuts, @UserIdInt int userId) {
+                final UserHandle user = UserHandle.of(userId);
+
+                final int n = mListeners.beginBroadcast();
+                for (int i = 0; i < n; i++) {
+                    IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                    UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+                    if (!isEnabledProfileOf(user, listeningUser, "onShortcutChanged")) continue;
+                    try {
+                        listener.onShortcutChanged(user, packageName,
+                                new ParceledListSlice<>(shortcuts));
+                    } catch (RemoteException re) {
+                        Slog.d(TAG, "Callback failed ", re);
+                    }
+                }
+                mListeners.finishBroadcast();
+            }
         }
 
         class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
new file mode 100644
index 0000000..423767a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -0,0 +1,1530 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IShortcutService;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutServiceInternal;
+import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.graphics.drawable.Icon;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.text.format.Time;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * TODO:
+ * - Make save async
+ *
+ * - Add Bitmap support
+ *
+ * - Implement updateShortcuts
+ *
+ * - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res
+ *
+ * - Pinned per each launcher package (multiple launchers)
+ *
+ * - Dev option to reset all counts for QA (for now use "adb shell cmd shortcut reset-throttling")
+ *
+ * - Load config from settings
+ */
+public class ShortcutService extends IShortcutService.Stub {
+    private static final String TAG = "ShortcutService";
+
+    private static final boolean DEBUG = true; // STOPSHIP if true
+    private static final boolean DEBUG_LOAD = true; // STOPSHIP if true
+
+    private static final int DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
+    private static final int DEFAULT_MAX_DAILY_UPDATES = 10;
+    private static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
+
+    private static final int SAVE_DELAY_MS = 5000; // in milliseconds.
+
+    @VisibleForTesting
+    static final String FILENAME_BASE_STATE = "shortcut_service.xml";
+
+    @VisibleForTesting
+    static final String DIRECTORY_PER_USER = "shortcut_service";
+
+    @VisibleForTesting
+    static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
+
+    private static final String DIRECTORY_BITMAPS = "bitmaps";
+
+    private static final String TAG_ROOT = "root";
+    private static final String TAG_LAST_RESET_TIME = "last_reset_time";
+    private static final String ATTR_VALUE = "value";
+
+    private final Context mContext;
+
+    private final Object mLock = new Object();
+
+    private final Handler mHandler;
+
+    @GuardedBy("mLock")
+    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
+
+    @GuardedBy("mLock")
+    private long mRawLastResetTime;
+
+    /**
+     * All the information relevant to shortcuts from a single package (per-user).
+     *
+     * TODO Move the persisting code to this class.
+     */
+    private static class PackageShortcuts {
+        /**
+         * All the shortcuts from the package, keyed on IDs.
+         */
+        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 mApiCallCountInner;
+
+        /**
+         * When {@link #mApiCallCountInner} was reset last time.
+         */
+        private long mLastResetTime;
+
+        /**
+         * @return the all shortcuts.  Note DO NOT add/remove or touch the flags of the result
+         * directly, which would cause {@link #mDynamicShortcutCount} to be out of sync.
+         */
+        @GuardedBy("mLock")
+        public ArrayMap<String, ShortcutInfo> getShortcuts() {
+            return mShortcuts;
+        }
+
+        /**
+         * Add a shortcut, or update one with the same ID, with taking over existing flags.
+         *
+         * It checks the max number of dynamic shortcuts.
+         */
+        @GuardedBy("mLock")
+        public void updateShortcutWithCapping(@NonNull ShortcutService s,
+                @NonNull ShortcutInfo newShortcut) {
+            final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+
+            int oldFlags = 0;
+            int newDynamicCount = mDynamicShortcutCount;
+
+            if (oldShortcut != null) {
+                oldFlags = oldShortcut.getFlags();
+                if (oldShortcut.isDynamic()) {
+                    newDynamicCount--;
+                }
+            }
+            if (newShortcut.isDynamic()) {
+                newDynamicCount++;
+            }
+            // Make sure there's still room.
+            s.enforceMaxDynamicShortcuts(newDynamicCount);
+
+            // Okay, make it dynamic and add.
+            newShortcut.addFlags(oldFlags);
+
+            mShortcuts.put(newShortcut.getId(), newShortcut);
+            mDynamicShortcutCount = newDynamicCount;
+        }
+
+        @GuardedBy("mLock")
+        public void deleteAllDynamicShortcuts() {
+            ArrayList<String> removeList = null; // Lazily initialize.
+
+            for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+                final ShortcutInfo si = mShortcuts.valueAt(i);
+
+                if (!si.isDynamic()) {
+                    continue;
+                }
+                if (si.isPinned()) {
+                    // Still pinned, so don't remove; just make it non-dynamic.
+                    si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+                } else {
+                    if (removeList == null) {
+                        removeList = new ArrayList<>();
+                    }
+                    removeList.add(si.getId());
+                }
+            }
+            if (removeList != null) {
+                for (int i = removeList.size() - 1 ; i >= 0; i--) {
+                    mShortcuts.remove(removeList.get(i));
+                }
+            }
+            mDynamicShortcutCount = 0;
+        }
+
+        @GuardedBy("mLock")
+        public void deleteDynamicWithId(@NonNull String shortcutId) {
+            final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
+
+            if (oldShortcut == null) {
+                return;
+            }
+            if (oldShortcut.isDynamic()) {
+                mDynamicShortcutCount--;
+            }
+            if (oldShortcut.isPinned()) {
+                oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+            } else {
+                mShortcuts.remove(shortcutId);
+            }
+        }
+
+        @GuardedBy("mLock")
+        public void pinAll(List<String> shortcutIds) {
+            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+                final ShortcutInfo shortcut = mShortcuts.get(shortcutIds.get(i));
+                if (shortcut != null) {
+                    shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+                }
+            }
+        }
+
+        /**
+         * Number of calls that the caller has made, since the last reset.
+         */
+        @GuardedBy("mLock")
+        public int getApiCallCount(@NonNull ShortcutService s) {
+            final long last = s.getLastResetTimeLocked();
+
+            // If not reset yet, then reset.
+            if (mLastResetTime < last) {
+                mApiCallCountInner = 0;
+                mLastResetTime = last;
+            }
+            return mApiCallCountInner;
+        }
+
+        /**
+         * If the caller app hasn't been throttled yet, increment {@link #mApiCallCountInner}
+         * and return true.  Otherwise just return false.
+         */
+        @GuardedBy("mLock")
+        public boolean tryApiCall(@NonNull ShortcutService s) {
+            if (getApiCallCount(s) >= s.mMaxDailyUpdates) {
+                return false;
+            }
+            mApiCallCountInner++;
+            return true;
+        }
+
+        @GuardedBy("mLock")
+        public void resetRateLimitingForCommandLine() {
+            mApiCallCountInner = 0;
+            mLastResetTime = 0;
+        }
+
+        /**
+         * Find all shortcuts that match {@code query}.
+         */
+        @GuardedBy("mLock")
+        public void findAll(@NonNull List<ShortcutInfo> result,
+                @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
+            for (int i = 0; i < mShortcuts.size(); i++) {
+                final ShortcutInfo si = mShortcuts.valueAt(i);
+                if (query == null || query.test(si)) {
+                    result.add(si.clone(cloneFlag));
+                }
+            }
+        }
+    }
+
+    /**
+     * User ID -> package name -> list of ShortcutInfos.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<ArrayMap<String, PackageShortcuts>> mShortcuts =
+            new SparseArray<>();
+
+    /**
+     * Max number of dynamic shortcuts that each application can have at a time.
+     */
+    @GuardedBy("mLock")
+    private int mMaxDynamicShortcuts;
+
+    /**
+     * Max number of updating API calls that each application can make a day.
+     */
+    @GuardedBy("mLock")
+    private int mMaxDailyUpdates;
+
+    /**
+     * Actual throttling-reset interval.  By default it's a day.
+     */
+    @GuardedBy("mLock")
+    private long mResetInterval;
+
+    public ShortcutService(Context context) {
+        mContext = Preconditions.checkNotNull(context);
+        LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
+        mHandler = new Handler(BackgroundThread.get().getLooper());
+    }
+
+    /**
+     * System service lifecycle.
+     */
+    public static final class Lifecycle extends SystemService {
+        final ShortcutService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+            mService = new ShortcutService(context);
+        }
+
+        @Override
+        public void onStart() {
+            publishBinderService(Context.SHORTCUT_SERVICE, mService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            mService.onBootPhase(phase);
+        }
+
+        @Override
+        public void onCleanupUser(int userHandle) {
+            synchronized (mService.mLock) {
+                mService.onCleanupUserInner(userHandle);
+            }
+        }
+
+        @Override
+        public void onStartUser(int userId) {
+            synchronized (mService.mLock) {
+                mService.onStartUserLocked(userId);
+            }
+        }
+    }
+
+    /** lifecycle event */
+    void onBootPhase(int phase) {
+        if (DEBUG) {
+            Slog.d(TAG, "onBootPhase: " + phase);
+        }
+        switch (phase) {
+            case SystemService.PHASE_LOCK_SETTINGS_READY:
+                initialize();
+                break;
+        }
+    }
+
+    /** lifecycle event */
+    void onStartUserLocked(int userId) {
+        // Preload
+        getUserShortcutsLocked(userId);
+    }
+
+    /** lifecycle event */
+    void onCleanupUserInner(int userId) {
+        // Unload
+        mShortcuts.delete(userId);
+    }
+
+    /** Return the base state file name */
+    private AtomicFile getBaseStateFile() {
+        final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
+        path.mkdirs();
+        return new AtomicFile(path);
+    }
+
+    /**
+     * Init the instance. (load the state file, etc)
+     */
+    private void initialize() {
+        synchronized (mLock) {
+            injectLoadConfigurationLocked();
+            loadBaseStateLocked();
+        }
+    }
+
+    // Test overrides it to inject different values.
+    @VisibleForTesting
+    void injectLoadConfigurationLocked() {
+        mResetInterval = DEFAULT_RESET_INTERVAL_SEC * 1000L;
+        mMaxDailyUpdates = DEFAULT_MAX_DAILY_UPDATES;
+        mMaxDynamicShortcuts = DEFAULT_MAX_SHORTCUTS_PER_APP;
+    }
+
+    // === Persistings ===
+
+    @Nullable
+    private String parseStringAttribute(XmlPullParser parser, String attribute) {
+        return parser.getAttributeValue(null, attribute);
+    }
+
+    private long parseLongAttribute(XmlPullParser parser, String attribute) {
+        final String value = parseStringAttribute(parser, attribute);
+        if (TextUtils.isEmpty(value)) {
+            return 0;
+        }
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Error parsing long " + value);
+            return 0;
+        }
+    }
+
+    @Nullable
+    private ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
+        final String value = parseStringAttribute(parser, attribute);
+        if (TextUtils.isEmpty(value)) {
+            return null;
+        }
+        return ComponentName.unflattenFromString(value);
+    }
+
+    @Nullable
+    private Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
+        final String value = parseStringAttribute(parser, attribute);
+        if (TextUtils.isEmpty(value)) {
+            return null;
+        }
+        try {
+            return Intent.parseUri(value, /* flags =*/ 0);
+        } catch (URISyntaxException e) {
+            Slog.e(TAG, "Error parsing intent", e);
+            return null;
+        }
+    }
+
+    private void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
+        if (TextUtils.isEmpty(value)) return;
+
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, value);
+        out.endTag(null, tag);
+    }
+
+    private void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
+        writeTagValue(out, tag, Long.toString(value));
+    }
+
+    private void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
+            throws IOException, XmlPullParserException {
+        if (bundle == null) return;
+
+        out.startTag(null, tag);
+        bundle.saveToXml(out);
+        out.endTag(null, tag);
+    }
+
+    private void writeAttr(XmlSerializer out, String name, String value) throws IOException {
+        if (TextUtils.isEmpty(value)) return;
+
+        out.attribute(null, name, value);
+    }
+
+    private void writeAttr(XmlSerializer out, String name, long value) throws IOException {
+        writeAttr(out, name, String.valueOf(value));
+    }
+
+    private void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
+        if (comp == null) return;
+        writeAttr(out, name, comp.flattenToString());
+    }
+
+    private void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
+        if (intent == null) return;
+
+        writeAttr(out, name, intent.toUri(/* flags =*/ 0));
+    }
+
+    @VisibleForTesting
+    void saveBaseStateLocked() {
+        final AtomicFile file = getBaseStateFile();
+        if (DEBUG) {
+            Slog.i(TAG, "Saving to " + file.getBaseFile());
+        }
+
+        FileOutputStream outs = null;
+        try {
+            outs = file.startWrite();
+
+            // Write to XML
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(outs, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_ROOT);
+
+            // Body.
+            writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
+
+            // Epilogue.
+            out.endTag(null, TAG_ROOT);
+            out.endDocument();
+
+            // Close.
+            file.finishWrite(outs);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+            file.failWrite(outs);
+        }
+    }
+
+    private void loadBaseStateLocked() {
+        mRawLastResetTime = 0;
+
+        final AtomicFile file = getBaseStateFile();
+        if (DEBUG) {
+            Slog.i(TAG, "Loading from " + file.getBaseFile());
+        }
+        try (FileInputStream in = file.openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, StandardCharsets.UTF_8.name());
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                final int depth = parser.getDepth();
+                // Check the root tag
+                final String tag = parser.getName();
+                if (depth == 1) {
+                    if (!TAG_ROOT.equals(tag)) {
+                        Slog.e(TAG, "Invalid root tag: " + tag);
+                        return;
+                    }
+                    continue;
+                }
+                // Assume depth == 2
+                switch (tag) {
+                    case TAG_LAST_RESET_TIME:
+                        mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
+                        break;
+                    default:
+                        Slog.e(TAG, "Invalid tag: " + tag);
+                        break;
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Use the default
+        } catch (IOException|XmlPullParserException e) {
+            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+
+            mRawLastResetTime = 0;
+        }
+        // Adjust the last reset time.
+        getLastResetTimeLocked();
+    }
+
+    private void saveUserLocked(@UserIdInt int userId) {
+        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
+        if (DEBUG) {
+            Slog.i(TAG, "Saving to " + path);
+        }
+        path.mkdirs();
+        final AtomicFile file = new AtomicFile(path);
+        FileOutputStream outs = null;
+        try {
+            outs = file.startWrite();
+
+            // Write to XML
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(outs, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_ROOT);
+
+            final ArrayMap<String, PackageShortcuts> packages = getUserShortcutsLocked(userId);
+
+            // Body.
+            for (int i = 0; i < packages.size(); i++) {
+                final String packageName = packages.keyAt(i);
+                final PackageShortcuts shortcuts = packages.valueAt(i);
+
+                // TODO Move this to PackageShortcuts.
+
+                out.startTag(null, "package");
+
+                writeAttr(out, "name", packageName);
+                writeAttr(out, "dynamic-count", shortcuts.mDynamicShortcutCount);
+                writeAttr(out, "call-count", shortcuts.mApiCallCountInner);
+                writeAttr(out, "last-reset", shortcuts.mLastResetTime);
+
+                final int size = shortcuts.getShortcuts().size();
+                for (int j = 0; j < size; j++) {
+                    saveShortcut(out, shortcuts.getShortcuts().valueAt(j));
+                }
+
+                out.endTag(null, "package");
+            }
+
+            // Epilogue.
+            out.endTag(null, TAG_ROOT);
+            out.endDocument();
+
+            // Close.
+            file.finishWrite(outs);
+        } catch (IOException|XmlPullParserException e) {
+            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+            file.failWrite(outs);
+        }
+    }
+
+    private void saveShortcut(XmlSerializer out, ShortcutInfo si)
+            throws IOException, XmlPullParserException {
+        out.startTag(null, "shortcut");
+        writeAttr(out, "id", si.getId());
+        // writeAttr(out, "package", si.getPackageName()); // not needed
+        writeAttr(out, "activity", si.getActivityComponent());
+        // writeAttr(out, "icon", si.getIcon());  // We don't save it.
+        writeAttr(out, "title", si.getTitle());
+        writeAttr(out, "intent", si.getIntent());
+        writeAttr(out, "weight", si.getWeight());
+        writeAttr(out, "timestamp", si.getLastChangedTimestamp());
+        writeAttr(out, "flags", si.getFlags());
+        writeAttr(out, "icon-res", si.getIconResourceId());
+        writeAttr(out, "bitmap-path", si.getBitmapPath());
+
+        writeTagExtra(out, "intent-extras", si.getIntentPersistableExtras());
+        writeTagExtra(out, "extras", si.getExtras());
+
+        out.endTag(null, "shortcut");
+    }
+
+    private static IOException throwForInvalidTag(int depth, String tag) throws IOException {
+        throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
+    }
+
+    @Nullable
+    private ArrayMap<String, PackageShortcuts> loadUserLocked(@UserIdInt int userId) {
+        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
+        if (DEBUG) {
+            Slog.i(TAG, "Loading from " + path);
+        }
+        path.mkdirs();
+        final AtomicFile file = new AtomicFile(path);
+
+        final FileInputStream in;
+        try {
+            in = file.openRead();
+        } catch (FileNotFoundException e) {
+            if (DEBUG) {
+                Slog.i(TAG, "Not found " + path);
+            }
+            return null;
+        }
+        final ArrayMap<String, PackageShortcuts> ret = new ArrayMap<String, PackageShortcuts>();
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, StandardCharsets.UTF_8.name());
+
+            String packageName = null;
+            PackageShortcuts shortcuts = null;
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                final int depth = parser.getDepth();
+
+                // TODO Move some of this to PackageShortcuts.
+
+                final String tag = parser.getName();
+                if (DEBUG_LOAD) {
+                    Slog.d(TAG, String.format("depth=%d type=%d name=%s",
+                            depth, type, tag));
+                }
+                switch (depth) {
+                    case 1: {
+                        if (TAG_ROOT.equals(tag)) {
+                            continue;
+                        }
+                        break;
+                    }
+                    case 2: {
+                        switch (tag) {
+                            case "package":
+                                packageName = parseStringAttribute(parser, "name");
+                                shortcuts = new PackageShortcuts();
+                                ret.put(packageName, shortcuts);
+
+                                shortcuts.mDynamicShortcutCount =
+                                        (int) parseLongAttribute(parser, "dynamic-count");
+                                shortcuts.mApiCallCountInner =
+                                        (int) parseLongAttribute(parser, "call-count");
+                                shortcuts.mLastResetTime = parseLongAttribute(parser, "last-reset");
+                                continue;
+                        }
+                        break;
+                    }
+                    case 3: {
+                        switch (tag) {
+                            case "shortcut":
+                                final ShortcutInfo si = parseShortcut(parser, packageName);
+                                shortcuts.mShortcuts.put(si.getId(), si);
+                                continue;
+                        }
+                        break;
+                    }
+                }
+                throwForInvalidTag(depth, tag);
+            }
+            return ret;
+        } catch (IOException|XmlPullParserException e) {
+            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+            return null;
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    private ShortcutInfo parseShortcut(XmlPullParser parser, String packgeName)
+            throws IOException, XmlPullParserException {
+        String id;
+        ComponentName activityComponent;
+        Icon icon;
+        String title;
+        Intent intent;
+        PersistableBundle intentPersistableExtras = null;
+        int weight;
+        PersistableBundle extras = null;
+        long lastChangedTimestamp;
+        int flags;
+        int iconRes;
+        String bitmapPath;
+
+        id = parseStringAttribute(parser, "id");
+        activityComponent = parseComponentNameAttribute(parser, "activity");
+        title = parseStringAttribute(parser, "title");
+        intent = parseIntentAttribute(parser, "intent");
+        weight = (int) parseLongAttribute(parser, "weight");
+        lastChangedTimestamp = (int) parseLongAttribute(parser, "timestamp");
+        flags = (int) parseLongAttribute(parser, "flags");
+        iconRes = (int) parseLongAttribute(parser, "icon-res");
+        bitmapPath = parseStringAttribute(parser, "bitmap-path");
+
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+            final int depth = parser.getDepth();
+            final String tag = parser.getName();
+            if (DEBUG_LOAD) {
+                Slog.d(TAG, String.format("  depth=%d type=%d name=%s",
+                        depth, type, tag));
+            }
+            switch (tag) {
+                case "intent-extras":
+                    intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
+                    continue;
+                case "extras":
+                    extras = PersistableBundle.restoreFromXml(parser);
+                    continue;
+            }
+            throw throwForInvalidTag(depth, tag);
+        }
+        return new ShortcutInfo(
+                id, packgeName, activityComponent, /* icon =*/ null, title, intent,
+                intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
+                iconRes, bitmapPath);
+    }
+
+    // TODO Actually make it async.
+    private void scheduleSaveBaseState() {
+        synchronized (mLock) {
+            saveBaseStateLocked();
+        }
+    }
+
+    // TODO Actually make it async.
+    private void scheduleSaveUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            saveUserLocked(userId);
+        }
+    }
+
+    /** Return the last reset time. */
+    long getLastResetTimeLocked() {
+        updateTimes();
+        return mRawLastResetTime;
+    }
+
+    /** Return the next reset time. */
+    long getNextResetTimeLocked() {
+        updateTimes();
+        return mRawLastResetTime + mResetInterval;
+    }
+
+    /**
+     * Update the last reset time.
+     */
+    private void updateTimes() {
+
+        final long now = injectCurrentTimeMillis();
+
+        final long prevLastResetTime = mRawLastResetTime;
+
+        if (mRawLastResetTime == 0) { // first launch.
+            // TODO Randomize??
+            mRawLastResetTime = now;
+        } else if (now < mRawLastResetTime) {
+            // Clock rewound.
+            // TODO Randomize??
+            mRawLastResetTime = now;
+        } else {
+            // TODO Do it properly.
+            while ((mRawLastResetTime + mResetInterval) <= now) {
+                mRawLastResetTime += mResetInterval;
+            }
+        }
+        if (prevLastResetTime != mRawLastResetTime) {
+            scheduleSaveBaseState();
+        }
+    }
+
+    /** Return the per-user state. */
+    @GuardedBy("mLock")
+    @NonNull
+    private ArrayMap<String, PackageShortcuts> getUserShortcutsLocked(@UserIdInt int userId) {
+        ArrayMap<String, PackageShortcuts> userPackages = mShortcuts.get(userId);
+        if (userPackages == null) {
+            userPackages = loadUserLocked(userId);
+            if (userPackages == null) {
+                userPackages = new ArrayMap<>();
+            }
+            mShortcuts.put(userId, userPackages);
+        }
+        return userPackages;
+    }
+
+    /** Return the per-user per-package state. */
+    @GuardedBy("mLock")
+    @NonNull
+    private PackageShortcuts getPackageShortcutsLocked(
+            @NonNull String packageName, @UserIdInt int userId) {
+        final ArrayMap<String, PackageShortcuts> userPackages = getUserShortcutsLocked(userId);
+        PackageShortcuts shortcuts = userPackages.get(packageName);
+        if (shortcuts == null) {
+            shortcuts = new PackageShortcuts();
+            userPackages.put(packageName, shortcuts);
+        }
+        return shortcuts;
+    }
+
+    // === Caller validation ===
+
+    private boolean isCallerSystem() {
+        final int callingUid = injectBinderCallingUid();
+         return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
+    }
+
+    private boolean isCallerShell() {
+        final int callingUid = injectBinderCallingUid();
+        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
+    }
+
+    private void enforceSystemOrShell() {
+        Preconditions.checkState(isCallerSystem() || isCallerShell(),
+                "Caller must be system or shell");
+    }
+
+    private void enforceShell() {
+        Preconditions.checkState(isCallerShell(), "Caller must be shell");
+    }
+
+    private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
+        Preconditions.checkStringNotEmpty(packageName, "packageName");
+
+        if (isCallerSystem()) {
+            return; // no check
+        }
+
+        final int callingUid = injectBinderCallingUid();
+
+        // Otherwise, make sure the arguments are valid.
+        if (UserHandle.getUserId(callingUid) != userId) {
+            throw new SecurityException("Invalid user-ID");
+        }
+        verifyCallingPackage(packageName);
+    }
+
+    private void verifyCallingPackage(@NonNull String packageName) {
+        Preconditions.checkStringNotEmpty(packageName, "packageName");
+
+        if (isCallerSystem()) {
+            return; // no check
+        }
+
+        if (injectGetPackageUid(packageName) == injectBinderCallingUid()) {
+            return; // Caller is valid.
+        }
+        throw new SecurityException("Caller UID= doesn't own " + packageName);
+    }
+
+    // Test overrides it.
+    int injectGetPackageUid(String packageName) {
+        try {
+
+            // TODO Is MATCH_UNINSTALLED_PACKAGES correct to get SD card app info?
+
+            return mContext.getPackageManager().getPackageUid(packageName,
+                    PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE
+                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        } catch (NameNotFoundException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
+     */
+    void enforceMaxDynamicShortcuts(int numShortcuts) {
+        if (numShortcuts > mMaxDynamicShortcuts) {
+            throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
+        }
+    }
+
+    /**
+     * - Sends a notification to LauncherApps
+     * - Write to file
+     */
+    private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) {
+        notifyListeners(packageName, userId);
+        scheduleSaveUser(userId);
+    }
+
+    private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
+        final ArrayList<ShortcutChangeListener> copy;
+        final List<ShortcutInfo> shortcuts = new ArrayList<>();
+        synchronized (mLock) {
+            copy = new ArrayList<>(mListeners);
+
+            getPackageShortcutsLocked(packageName, userId)
+                    .findAll(shortcuts, /* query =*/ null, ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+        }
+        for (int i = copy.size() - 1; i >= 0; i--) {
+            copy.get(i).onShortcutChanged(packageName, shortcuts, userId);
+        }
+    }
+
+    /**
+     * Clean up / validate an incoming shortcut.
+     * - Make sure all mandatory fields are set.
+     * - Make sure the intent's extras are persistable, and them to set
+     *  {@link ShortcutInfo#mIntentPersistableExtras}.  Also clear its extras.
+     * - Clear flags.
+     */
+    private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut) {
+        Preconditions.checkNotNull(shortcut, "Null shortcut detected");
+        if (shortcut.getActivityComponent() != null) {
+            Preconditions.checkState(
+                    shortcut.getPackageName().equals(
+                            shortcut.getActivityComponent().getPackageName()),
+                    "Activity package name mismatch");
+        }
+
+        shortcut.enforceMandatoryFields();
+
+        final Intent intent = shortcut.getIntent();
+        final Bundle intentExtras = intent.getExtras();
+        if (intentExtras != null && intentExtras.size() > 0) {
+            intent.replaceExtras((Bundle) null);
+
+            // PersistableBundle's constructor will throw IllegalArgumentException if original
+            // extras contain something not persistable.
+            shortcut.setIntentPersistableExtras(new PersistableBundle(intentExtras));
+        }
+
+        // TODO Save the icon
+        shortcut.setIcon(null);
+
+        shortcut.setFlags(0);
+    }
+
+    // === APIs ===
+
+    @Override
+    public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+
+        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
+        final int size = newShortcuts.size();
+
+        synchronized (mLock) {
+            final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
+
+            // Throttling.
+            if (!ps.tryApiCall(this)) {
+                return false;
+            }
+            enforceMaxDynamicShortcuts(size);
+
+            // Validate the shortcuts.
+            for (int i = 0; i < size; i++) {
+                fixUpIncomingShortcutInfo(newShortcuts.get(i));
+            }
+
+            // First, remove all un-pinned; dynamic shortcuts
+            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);
+                newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
+                ps.updateShortcutWithCapping(this, newShortcut);
+            }
+        }
+        userPackageChanged(packageName, userId);
+
+        return true;
+    }
+
+    @Override
+    public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+
+        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
+
+        synchronized (mLock) {
+
+            if (true) {
+                throw new RuntimeException("not implemented yet");
+            }
+
+            // TODO Similar to setDynamicShortcuts, but don't add new ones, and don't change flags.
+            // Update non-null fields only.
+        }
+        userPackageChanged(packageName, userId);
+
+        return true;
+    }
+
+    @Override
+    public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+
+        synchronized (mLock) {
+            final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
+
+            // Throttling.
+            if (!ps.tryApiCall(this)) {
+                return false;
+            }
+
+            // Validate the shortcut.
+            fixUpIncomingShortcutInfo(newShortcut);
+
+            // Add it.
+            newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
+            ps.updateShortcutWithCapping(this, newShortcut);
+        }
+        userPackageChanged(packageName, userId);
+
+        return true;
+    }
+
+    @Override
+    public void deleteDynamicShortcut(String packageName, String shortcutId,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+        Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided");
+
+        synchronized (mLock) {
+            getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(shortcutId);
+        }
+        userPackageChanged(packageName, userId);
+    }
+
+    @Override
+    public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+
+        synchronized (mLock) {
+            getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
+        }
+        userPackageChanged(packageName, userId);
+    }
+
+    @Override
+    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+        synchronized (mLock) {
+            return getShortcutsWithQueryLocked(
+                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
+                    ShortcutInfo::isDynamic);
+        }
+    }
+
+    @Override
+    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+        synchronized (mLock) {
+            return getShortcutsWithQueryLocked(
+                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
+                    ShortcutInfo::isPinned);
+        }
+    }
+
+    private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
+            @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
+
+        final ArrayList<ShortcutInfo> ret = new ArrayList<>();
+
+        getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags);
+
+        return new ParceledListSlice<>(ret);
+    }
+
+    @Override
+    public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId)
+            throws RemoteException {
+        verifyCaller(packageName, userId);
+
+        return mMaxDynamicShortcuts;
+    }
+
+    @Override
+    public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+
+        synchronized (mLock) {
+            return mMaxDailyUpdates
+                    - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
+        }
+    }
+
+    @Override
+    public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+
+        synchronized (mLock) {
+            return getNextResetTimeLocked();
+        }
+    }
+
+    /**
+     * Reset all throttling, for developer options and command line.  Only system/shell can call it.
+     */
+    @Override
+    public void resetThrottling() {
+        enforceSystemOrShell();
+
+        resetThrottlingInner();
+    }
+
+    @VisibleForTesting
+    void resetThrottlingInner() {
+        synchronized (mLock) {
+            mRawLastResetTime = injectCurrentTimeMillis();
+        }
+        scheduleSaveBaseState();
+    }
+
+    /**
+     * Entry point from {@link LauncherApps}.
+     */
+    private class LocalService extends ShortcutServiceInternal {
+        @Override
+        public List<ShortcutInfo> getShortcuts(
+                @NonNull String callingPackage, long changedSince,
+                @Nullable String packageName, @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;
+
+            synchronized (mLock) {
+                if (packageName != null) {
+                    getShortcutsInnerLocked(packageName, changedSince, componentName, queryFlags,
+                            userId, ret, cloneFlag);
+                } else {
+                    final ArrayMap<String, PackageShortcuts> packages =
+                            getUserShortcutsLocked(userId);
+                    for (int i = 0; i < packages.size(); i++) {
+                        getShortcutsInnerLocked(
+                                packages.keyAt(i),
+                                changedSince, componentName, queryFlags, userId, ret, cloneFlag);
+                    }
+                }
+            }
+            return ret;
+        }
+
+        private void getShortcutsInnerLocked(@Nullable String packageName,long changedSince,
+                @Nullable ComponentName componentName, int queryFlags,
+                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
+            getPackageShortcutsLocked(packageName, userId).findAll(ret,
+                    (ShortcutInfo si) -> {
+                        if (si.getLastChangedTimestamp() < changedSince) {
+                            return false;
+                        }
+                        if (componentName != null
+                                && !componentName.equals(si.getActivityComponent())) {
+                            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;
+                    }, cloneFlag);
+        }
+
+        @Override
+        public List<ShortcutInfo> getShortcutInfo(
+                @NonNull String callingPackage,
+                @NonNull String packageName, @Nullable List<String> ids, int userId) {
+            // Calling permission must be checked by LauncherAppsImpl.
+            Preconditions.checkStringNotEmpty(packageName, "packageName");
+
+            final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
+            final ArraySet<String> idSet = new ArraySet<>(ids);
+            synchronized (mLock) {
+                getPackageShortcutsLocked(packageName, userId).findAll(ret,
+                        (ShortcutInfo si) -> idSet.contains(si.getId()),
+                        ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+            }
+            return ret;
+        }
+
+        @Override
+        public void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName,
+                @NonNull List<String> shortcutIds, int userId) {
+            // Calling permission must be checked by LauncherAppsImpl.
+            Preconditions.checkStringNotEmpty(packageName, "packageName");
+            Preconditions.checkNotNull(shortcutIds, "shortcutIds");
+
+            synchronized (mLock) {
+                getPackageShortcutsLocked(packageName, userId).pinAll(shortcutIds);
+            }
+            userPackageChanged(packageName, userId);
+        }
+
+        @Override
+        public Intent createShortcutIntent(@NonNull String callingPackage,
+                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+            // Calling permission must be checked by LauncherAppsImpl.
+            Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
+            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
+
+            synchronized (mLock) {
+                final ShortcutInfo fullShortcut =
+                        getPackageShortcutsLocked(packageName, userId)
+                        .getShortcuts().get(shortcutId);
+                if (fullShortcut == null) {
+                    return null;
+                } else {
+                    final Intent intent = fullShortcut.getIntent();
+                    final PersistableBundle extras = fullShortcut.getIntentPersistableExtras();
+                    if (extras != null) {
+                        intent.replaceExtras(new Bundle(extras));
+                    }
+
+                    return intent;
+                }
+            }
+        }
+
+        @Override
+        public void addListener(@NonNull ShortcutChangeListener listener) {
+            synchronized (mLock) {
+                mListeners.add(Preconditions.checkNotNull(listener));
+            }
+        }
+    }
+
+    // === Dump ===
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump UserManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " without permission "
+                    + android.Manifest.permission.DUMP);
+            return;
+        }
+        dumpInner(pw);
+    }
+
+    @VisibleForTesting
+    void dumpInner(PrintWriter pw) {
+        synchronized (mLock) {
+            final long now = injectCurrentTimeMillis();
+            pw.print("Now: [");
+            pw.print(now);
+            pw.print("] ");
+            pw.print(formatTime(now));
+            pw.print("  Raw last reset: [");
+            pw.print(mRawLastResetTime);
+            pw.print("] ");
+            pw.print(formatTime(mRawLastResetTime));
+
+            final long last = getLastResetTimeLocked();
+            final long next = getNextResetTimeLocked();
+            pw.print("  Last reset: [");
+            pw.print(last);
+            pw.print("] ");
+            pw.print(formatTime(last));
+
+            pw.print("  Next reset: [");
+            pw.print(next);
+            pw.print("] ");
+            pw.print(formatTime(next));
+            pw.println();
+
+            pw.println();
+
+            for (int i = 0; i < mShortcuts.size(); i++) {
+                dumpUserLocked(pw, mShortcuts.keyAt(i));
+            }
+
+        }
+    }
+
+    private void dumpUserLocked(PrintWriter pw, int userId) {
+        pw.print("  User: ");
+        pw.print(userId);
+        pw.println();
+
+        final ArrayMap<String, PackageShortcuts> packages = mShortcuts.get(userId);
+        if (packages == null) {
+            return;
+        }
+        for (int j = 0; j < packages.size(); j++) {
+            dumpPackageLocked(pw, userId, packages.keyAt(j));
+        }
+        pw.println();
+    }
+
+    private void dumpPackageLocked(PrintWriter pw, int userId, String packageName) {
+        final PackageShortcuts shortcuts = mShortcuts.get(userId).get(packageName);
+        if (shortcuts == null) {
+            return;
+        }
+
+        pw.print("    Package: ");
+        pw.print(packageName);
+        pw.println();
+
+        pw.print("      Calls: ");
+        pw.print(shortcuts.getApiCallCount(this));
+        pw.println();
+
+        // This should be after getApiCallCount(), which may update it.
+        pw.print("      Last reset: [");
+        pw.print(shortcuts.mLastResetTime);
+        pw.print("] ");
+        pw.print(formatTime(shortcuts.mLastResetTime));
+        pw.println();
+
+        pw.println("      Shortcuts:");
+        final int size = shortcuts.getShortcuts().size();
+        for (int i = 0; i < size; i++) {
+            pw.print("        ");
+            pw.println(shortcuts.getShortcuts().valueAt(i).toInsecureString());
+        }
+    }
+
+    private static String formatTime(long time) {
+        Time tobj = new Time();
+        tobj.set(time);
+        return tobj.format("%Y-%m-%d %H:%M:%S");
+    }
+
+    // === Shell support ===
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException {
+
+        enforceShell();
+
+        (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
+    }
+
+    /**
+     * Handle "adb shell cmd".
+     */
+    private class MyShellCommand extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            if (cmd == null) {
+                return handleDefaultCommands(cmd);
+            }
+            final PrintWriter pw = getOutPrintWriter();
+            switch(cmd) {
+                case "reset-package-throttling":
+                    return handleResetPackageThrottling();
+                case "reset-throttling":
+                    return handleResetThrottling();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        }
+
+        @Override
+        public void onHelp() {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("Usage: cmd shortcut COMMAND [options ...]");
+            pw.println();
+            pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
+            pw.println("    Reset throttling for a package");
+            pw.println();
+            pw.println("cmd shortcut reset-throttling");
+            pw.println("    Reset throttling for all packages and users");
+            pw.println();
+        }
+
+        private int handleResetThrottling() {
+            resetThrottling();
+            return 0;
+        }
+
+        private int handleResetPackageThrottling() {
+            final PrintWriter pw = getOutPrintWriter();
+
+            int userId = UserHandle.USER_SYSTEM;
+            String opt;
+            while ((opt = getNextOption()) != null) {
+                switch (opt) {
+                    case "--user":
+                        userId = UserHandle.parseUserArg(getNextArgRequired());
+                        break;
+                    default:
+                        pw.println("Error: Unknown option: " + opt);
+                        return 1;
+                }
+            }
+            final String packageName = getNextArgRequired();
+
+            synchronized (mLock) {
+                getPackageShortcutsLocked(packageName, userId).resetRateLimitingForCommandLine();
+                saveUserLocked(userId);
+            }
+
+            return 0;
+        }
+    }
+
+    // === Unit test support ===
+
+    // Injection point.
+    long injectCurrentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    // Injection point.
+    int injectBinderCallingUid() {
+        return getCallingUid();
+    }
+
+    File injectSystemDataPath() {
+        return Environment.getDataSystemDirectory();
+    }
+
+    File injectUserDataPath(@UserIdInt int userId) {
+        return new File(Environment.getDataSystemDeDirectory(userId), DIRECTORY_PER_USER);
+    }
+
+    @VisibleForTesting
+    SparseArray<ArrayMap<String, PackageShortcuts>> getShortcutsForTest() {
+        return mShortcuts;
+    }
+
+    @VisibleForTesting
+    void setMaxDynamicShortcutsForTest(int max) {
+        mMaxDynamicShortcuts = max;
+    }
+
+    @VisibleForTesting
+    void setMaxDailyUpdatesForTest(int max) {
+        mMaxDailyUpdates = max;
+    }
+
+    @VisibleForTesting
+    public void setResetIntervalForTest(long interval) {
+        mResetInterval = interval;
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e51a2e1..1603f1c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -28,6 +28,8 @@
 import static android.view.WindowManager.DOCKED_TOP;
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
 import static android.view.WindowManager.LayoutParams.*;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
@@ -1239,7 +1241,7 @@
                             + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
                 mScreenshotChordVolumeDownKeyConsumed = true;
                 cancelPendingPowerKeyAction();
-
+                mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
                 mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
             }
         }
@@ -1269,12 +1271,20 @@
         }
     };
 
-    private final Runnable mScreenshotRunnable = new Runnable() {
+    private class ScreenshotRunnable implements Runnable {
+        private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
+
+        public void setScreenshotType(int screenshotType) {
+            mScreenshotType = screenshotType;
+        }
+
         @Override
         public void run() {
-            takeScreenshot();
+            takeScreenshot(mScreenshotType);
         }
-    };
+    }
+
+    private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();
 
     @Override
     public void showGlobalActions() {
@@ -2304,29 +2314,33 @@
         case TYPE_NAVIGATION_BAR_PANEL:
             // some panels (e.g. search) need to show on top of the navigation bar
             return 22;
+        case TYPE_SCREENSHOT:
+            // screenshot selection layer shouldn't go above system error, but it should cover
+            // navigation bars at the very least.
+            return 23;
         case TYPE_SYSTEM_ERROR:
             // system-level error dialogs
-            return 23;
+            return 24;
         case TYPE_MAGNIFICATION_OVERLAY:
             // used to highlight the magnified portion of a display
-            return 24;
+            return 25;
         case TYPE_DISPLAY_OVERLAY:
             // used to simulate secondary display devices
-            return 25;
+            return 26;
         case TYPE_DRAG:
             // the drag layer: input for drag-and-drop is associated with this window,
             // which sits above all other focusable windows
-            return 26;
+            return 27;
         case TYPE_ACCESSIBILITY_OVERLAY:
             // overlay put by accessibility services to intercept user interaction
-            return 27;
-        case TYPE_SECURE_SYSTEM_OVERLAY:
             return 28;
-        case TYPE_BOOT_PROGRESS:
+        case TYPE_SECURE_SYSTEM_OVERLAY:
             return 29;
+        case TYPE_BOOT_PROGRESS:
+            return 30;
         case TYPE_POINTER:
             // the (mouse) pointer layer
-            return 30;
+            return 31;
         }
         Log.e(TAG, "Unknown window type: " + type);
         return 2;
@@ -3026,6 +3040,15 @@
                     }
                 }
             }
+        } else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed()
+                && event.isCtrlPressed()) {
+            if (down && repeatCount == 0) {
+                int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
+                        : TAKE_SCREENSHOT_FULLSCREEN;
+                mScreenshotRunnable.setScreenshotType(type);
+                mHandler.post(mScreenshotRunnable);
+                return -1;
+            }
         } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) {
             if (down) {
                 if (repeatCount == 0) {
@@ -3073,6 +3096,7 @@
             }
         } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
             if (down && repeatCount == 0) {
+                mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
                 mHandler.post(mScreenshotRunnable);
             }
             return -1;
@@ -3360,8 +3384,8 @@
             throws RemoteException {
         synchronized (mLock) {
             IShortcutService service = mShortcutKeyServices.get(shortcutCode);
-            if (service != null && service.asBinder().isBinderAlive()) {
-                    throw new RemoteException("Key already exists.");
+            if (service != null && service.asBinder().pingBinder()) {
+                throw new RemoteException("Key already exists.");
             }
 
             mShortcutKeyServices.put(shortcutCode, shortcutService);
@@ -4402,9 +4426,11 @@
                                     "Laying out navigation bar window: (%d,%d - %d,%d)",
                                     pf.left, pf.top, pf.right, pf.bottom));
                 } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
-                                || attrs.type == TYPE_BOOT_PROGRESS)
+                                || attrs.type == TYPE_BOOT_PROGRESS
+                                || attrs.type == TYPE_SCREENSHOT)
                         && ((fl & FLAG_FULLSCREEN) != 0)) {
-                    // Fullscreen secure system overlays get what they ask for.
+                    // Fullscreen secure system overlays get what they ask for. Screenshot region
+                    // selection overlay should also expand to full screen.
                     pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
                     pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
                     pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
@@ -5159,7 +5185,7 @@
     };
 
     // Assume this is called from the Handler thread.
-    private void takeScreenshot() {
+    private void takeScreenshot(final int screenshotType) {
         synchronized (mScreenshotLock) {
             if (mScreenshotConnection != null) {
                 return;
@@ -5176,7 +5202,7 @@
                             return;
                         }
                         Messenger messenger = new Messenger(service);
-                        Message msg = Message.obtain(null, 1);
+                        Message msg = Message.obtain(null, screenshotType);
                         final ServiceConnection myConn = this;
                         Handler h = new Handler(mHandler.getLooper()) {
                             @Override
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index e5c5b2bc..858f7c7 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -81,6 +81,7 @@
     private boolean mBound;
     private long mScheduledRestartUptimeMillis;
     private long mMaximumTimeToLock; // from DevicePolicyManager
+    private boolean mPendingSuccessfulUnlock = false;
 
     // Trust state
     private boolean mTrusted;
@@ -234,6 +235,11 @@
             setCallback(mCallback);
             updateDevicePolicyFeatures();
 
+            if (mPendingSuccessfulUnlock) {
+                onUnlockAttempt(true);
+                mPendingSuccessfulUnlock = false;
+            }
+
             if (mTrustManagerService.isDeviceLockedInner(mUserId)) {
                 onDeviceLocked();
             } else {
@@ -302,7 +308,11 @@
      */
     public void onUnlockAttempt(boolean successful) {
         try {
-            if (mTrustAgentService != null) mTrustAgentService.onUnlockAttempt(successful);
+            if (mTrustAgentService != null) {
+                mTrustAgentService.onUnlockAttempt(successful);
+            } else {
+                mPendingSuccessfulUnlock = successful;
+            }
         } catch (RemoteException e) {
             onError(e);
         }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index b54e866..984fb76 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -19,7 +19,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
 import com.android.server.SystemService;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -104,7 +103,7 @@
     private static final int MSG_SET_DEVICE_LOCKED = 10;
     private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 11;
 
-    public static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
+    private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
 
     private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
     private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
@@ -136,13 +135,7 @@
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mLockPatternUtils = new LockPatternUtils(context);
-
-        mStrongAuthTracker = new StrongAuthTracker(context) {
-            @Override
-            public void onStrongAuthRequiredChanged(int userId) {
-                refreshAgentList(userId);
-            }
-        };
+        mStrongAuthTracker = new StrongAuthTracker(context);
     }
 
     @Override
@@ -231,24 +224,24 @@
                 TRUST_USUALLY_MANAGED_FLUSH_DELAY);
     }
 
-    void refreshAgentList(int userId) {
-        if (DEBUG) Slog.d(TAG, "refreshAgentList()");
+    void refreshAgentList(int userIdOrAll) {
+        if (DEBUG) Slog.d(TAG, "refreshAgentList(" + userIdOrAll + ")");
         if (!mTrustAgentsCanRun) {
             return;
         }
-        if (userId != UserHandle.USER_ALL && userId < UserHandle.USER_SYSTEM) {
-            Log.e(TAG, "refreshAgentList(userId=" + userId + "): Invalid user handle,"
+        if (userIdOrAll != UserHandle.USER_ALL && userIdOrAll < UserHandle.USER_SYSTEM) {
+            Log.e(TAG, "refreshAgentList(userId=" + userIdOrAll + "): Invalid user handle,"
                     + " must be USER_ALL or a specific user.", new Throwable("here"));
-            userId = UserHandle.USER_ALL;
+            userIdOrAll = UserHandle.USER_ALL;
         }
         PackageManager pm = mContext.getPackageManager();
 
         List<UserInfo> userInfos;
-        if (userId == UserHandle.USER_ALL) {
+        if (userIdOrAll == UserHandle.USER_ALL) {
             userInfos = mUserManager.getUsers(true /* excludeDying */);
         } else {
             userInfos = new ArrayList<>();
-            userInfos.add(mUserManager.getUserInfo(userId));
+            userInfos.add(mUserManager.getUserInfo(userIdOrAll));
         }
         LockPatternUtils lockPatternUtils = mLockPatternUtils;
 
@@ -261,7 +254,7 @@
             if (!userInfo.supportsSwitchToByUser()) continue;
             if (!mActivityManager.isUserRunning(userInfo.id)) continue;
             if (!lockPatternUtils.isSecure(userInfo.id)) continue;
-            if (!mStrongAuthTracker.isTrustAllowedForUser(userInfo.id)) continue;
+            if (!mStrongAuthTracker.canAgentsRunForUser(userInfo.id)) continue;
             DevicePolicyManager dpm = lockPatternUtils.getDevicePolicyManager();
             int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userInfo.id);
             final boolean disableTrustAgents =
@@ -302,7 +295,7 @@
         boolean trustMayHaveChanged = false;
         for (int i = 0; i < obsoleteAgents.size(); i++) {
             AgentInfo info = obsoleteAgents.valueAt(i);
-            if (userId == UserHandle.USER_ALL || userId == info.userId) {
+            if (userIdOrAll == UserHandle.USER_ALL || userIdOrAll == info.userId) {
                 if (info.agent.isManagingTrust()) {
                     trustMayHaveChanged = true;
                 }
@@ -312,10 +305,10 @@
         }
 
         if (trustMayHaveChanged) {
-            if (userId == UserHandle.USER_ALL) {
+            if (userIdOrAll == UserHandle.USER_ALL) {
                 updateTrustAll();
             } else {
-                updateTrust(userId, 0);
+                updateTrust(userIdOrAll, 0);
             }
         }
     }
@@ -578,6 +571,10 @@
     }
 
     private void dispatchUnlockAttempt(boolean successful, int userId) {
+        if (successful) {
+            mStrongAuthTracker.allowTrustFromUnlock(userId);
+        }
+
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
             if (info.userId == userId) {
@@ -608,6 +605,10 @@
     }
 
     private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) {
+        if (DEBUG) {
+            Log.i(TAG, "onTrustChanged(" + enabled + ", " + userId + ", 0x"
+                    + Integer.toHexString(flags) + ")");
+        }
         if (!enabled) flags = 0;
         for (int i = 0; i < mTrustListeners.size(); i++) {
             try {
@@ -623,6 +624,9 @@
     }
 
     private void dispatchOnTrustManagedChanged(boolean managed, int userId) {
+        if (DEBUG) {
+            Log.i(TAG, "onTrustManagedChanged(" + managed + ", " + userId + ")");
+        }
         for (int i = 0; i < mTrustListeners.size(); i++) {
             try {
                 mTrustListeners.get(i).onTrustManagedChanged(managed, userId);
@@ -980,4 +984,61 @@
                     null /* scheduler */);
         }
     }
+
+    private class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+        SparseBooleanArray mStartFromSuccessfulUnlock = new SparseBooleanArray();
+
+        public StrongAuthTracker(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStrongAuthRequiredChanged(int userId) {
+            mStartFromSuccessfulUnlock.delete(userId);
+
+            if (DEBUG) {
+                Log.i(TAG, "onStrongAuthRequiredChanged(" + userId + ") ->"
+                        + " trustAllowed=" + isTrustAllowedForUser(userId)
+                        + " agentsCanRun=" + canAgentsRunForUser(userId));
+            }
+
+            refreshAgentList(userId);
+
+            // The list of active trust agents may not have changed, if there was a previous call
+            // to allowTrustFromUnlock, so we update the trust here too.
+            updateTrust(userId, 0 /* flags */);
+        }
+
+        boolean canAgentsRunForUser(int userId) {
+            return mStartFromSuccessfulUnlock.get(userId)
+                    || super.isTrustAllowedForUser(userId);
+        }
+
+        /**
+         * Temporarily suppress strong auth requirements for {@param userId} until strong auth
+         * changes again. Must only be called when we know about a successful unlock already
+         * before the underlying StrongAuthTracker.
+         *
+         * Note that this only changes whether trust agents can be started, not the actual trusted
+         * value.
+         */
+        void allowTrustFromUnlock(int userId) {
+            if (userId < UserHandle.USER_SYSTEM) {
+                throw new IllegalArgumentException("userId must be a valid user: " + userId);
+            }
+            boolean previous = canAgentsRunForUser(userId);
+            mStartFromSuccessfulUnlock.put(userId, true);
+
+            if (DEBUG) {
+                Log.i(TAG, "allowTrustFromUnlock(" + userId + ") ->"
+                        + " trustAllowed=" + isTrustAllowedForUser(userId)
+                        + " agentsCanRun=" + canAgentsRunForUser(userId));
+            }
+
+            if (canAgentsRunForUser(userId) != previous) {
+                refreshAgentList(userId);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 49aaa4a..30442bc 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1292,6 +1292,7 @@
         @Override
         public void unblockContent(
                 IBinder sessionToken, String unblockedRating, int userId) {
+            ensureParentalControlsPermission();
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
                     userId, "unblockContent");
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 2731f429..9795c93 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -129,6 +129,7 @@
     boolean mAlwaysFocusable;
 
     boolean mAppStopped;
+    int mPendingRelaunchCount;
 
     ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
 
@@ -531,6 +532,26 @@
         }
     }
 
+    boolean isRelaunching() {
+        return mPendingRelaunchCount > 0;
+    }
+
+    void startRelaunching() {
+        if (canFreezeBounds()) {
+            freezeBounds();
+        }
+        mPendingRelaunchCount++;
+    }
+
+    void finishRelaunching() {
+        if (canFreezeBounds()) {
+            unfreezeBounds();
+        }
+        if (mPendingRelaunchCount > 0) {
+            mPendingRelaunchCount--;
+        }
+    }
+
     void addWindow(WindowState w) {
         for (int i = allAppWindows.size() - 1; i >= 0; i--) {
             WindowState candidate = allAppWindows.get(i);
@@ -579,20 +600,26 @@
         }
     }
 
+    private boolean canFreezeBounds() {
+        // For freeform windows, we can't freeze the bounds at the moment because this would make
+        // the resizing unresponsive.
+        return mTask != null && !mTask.inFreeformWorkspace();
+    }
+
     /**
      * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
      * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
      * if they change in the meantime. If the bounds are already frozen, the bounds will be frozen
      * with a queue.
      */
-    void freezeBounds() {
+    private void freezeBounds() {
         mFrozenBounds.offer(new Rect(mTask.mPreparedFrozenBounds));
     }
 
     /**
      * Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
      */
-    void unfreezeBounds() {
+    private void unfreezeBounds() {
         mFrozenBounds.remove();
         for (int i = windows.size() - 1; i >= 0; i--) {
             final WindowState win = windows.get(i);
@@ -658,7 +685,10 @@
                     pw.print(" startingMoved="); pw.println(startingMoved);
         }
         if (!mFrozenBounds.isEmpty()) {
-            pw.print(prefix); pw.print("mFrozenBounds="); pw.print(mFrozenBounds);
+            pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds);
+        }
+        if (mPendingRelaunchCount != 0) {
+            pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b64aaa8..a998bc3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -24,6 +24,9 @@
 import android.app.ActivityManagerNative;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -118,6 +121,7 @@
 import android.view.inputmethod.InputMethodManagerInternal;
 import android.widget.Toast;
 
+import com.android.internal.R;
 import com.android.internal.app.IAssistScreenshotReceiver;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IShortcutService;
@@ -2340,6 +2344,7 @@
                 mTokenMap.remove(token.token);
             } else if (atoken != null) {
                 atoken.firstWindowDrawn = false;
+                atoken.allDrawn = false;
             }
         }
 
@@ -4251,6 +4256,28 @@
                         TAG_WM, "No longer Stopped: " + wtoken);
                 wtoken.mAppStopped = false;
                 wtoken.setWindowsExiting(false);
+                mOpeningApps.add(wtoken);
+                wtoken.startingMoved = false;
+
+                // If the token is currently hidden (should be the
+                // common case), then we need to set up to wait for
+                // its windows to be ready.
+                if (wtoken.hidden) {
+                    wtoken.allDrawn = false;
+                    wtoken.deferClearAllDrawn = false;
+                    wtoken.waitingToShow = true;
+
+                    if (wtoken.clientHidden) {
+                        // In the case where we are making an app visible
+                        // but holding off for a transition, we still need
+                        // to tell the client to make its windows visible so
+                        // they get drawn.  Otherwise, we will wait on
+                        // performing the transition until all windows have
+                        // been drawn, they never will be, and we are sad.
+                        wtoken.clientHidden = false;
+                        wtoken.sendAppVisibilityToClients();
+                    }
+                }
             }
 
             // If we are preparing an app transition, then delay changing
@@ -4268,29 +4295,7 @@
                 }
                 wtoken.inPendingTransaction = true;
                 if (visible) {
-                    mOpeningApps.add(wtoken);
-                    wtoken.startingMoved = false;
                     wtoken.mEnteringAnimation = true;
-
-                    // If the token is currently hidden (should be the
-                    // common case), then we need to set up to wait for
-                    // its windows to be ready.
-                    if (wtoken.hidden) {
-                        wtoken.allDrawn = false;
-                        wtoken.deferClearAllDrawn = false;
-                        wtoken.waitingToShow = true;
-
-                        if (wtoken.clientHidden) {
-                            // In the case where we are making an app visible
-                            // but holding off for a transition, we still need
-                            // to tell the client to make its windows visible so
-                            // they get drawn.  Otherwise, we will wait on
-                            // performing the transition until all windows have
-                            // been drawn, they never will be, and we are sad.
-                            wtoken.clientHidden = false;
-                            wtoken.sendAppVisibilityToClients();
-                        }
-                    }
                 } else {
                     wtoken.setWindowsExiting(true);
                     mClosingApps.add(wtoken);
@@ -7468,6 +7473,35 @@
         return mCurrentFocus;
     }
 
+    private void showAuditSafeModeNotification() {
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(Intent.ACTION_VIEW,
+                           Uri.parse("https://support.google.com/nexus/answer/2852139")), 0);
+
+        String title = mContext.getString(R.string.audit_safemode_notification);
+
+        Notification notification = new Notification.Builder(mContext)
+                .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
+                .setWhen(0)
+                .setOngoing(true)
+                .setTicker(title)
+                .setLocalOnly(true)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setContentTitle(title)
+                .setContentText(mContext.getString(R.string.audit_safemode_notification_details))
+                .setContentIntent(pendingIntent)
+                .build();
+
+        NotificationManager notificationManager = (NotificationManager) mContext
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+
+        notificationManager.notifyAsUser(null, R.string.audit_safemode_notification, notification,
+                UserHandle.ALL);
+    }
+
     public boolean detectSafeMode() {
         if (!mInputMonitor.waitForInputDevicesReady(
                 INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) {
@@ -7500,6 +7534,7 @@
 
                     if (auditSafeMode >= buildDate) {
                         mSafeMode = true;
+                        showAuditSafeModeNotification();
                     } else {
                         SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
                         SystemProperties.set(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, "");
@@ -9636,8 +9671,8 @@
     public void notifyAppRelaunching(IBinder token) {
         synchronized (mWindowMap) {
             AppWindowToken appWindow = findAppWindowToken(token);
-            if (canFreezeBounds(appWindow)) {
-                appWindow.freezeBounds();
+            if (appWindow != null) {
+                appWindow.startRelaunching();
             }
         }
     }
@@ -9645,20 +9680,12 @@
     public void notifyAppRelaunchingFinished(IBinder token) {
         synchronized (mWindowMap) {
             AppWindowToken appWindow = findAppWindowToken(token);
-            if (canFreezeBounds(appWindow)) {
-                appWindow.unfreezeBounds();
+            if (appWindow != null) {
+                appWindow.finishRelaunching();
             }
         }
     }
 
-    private boolean canFreezeBounds(AppWindowToken appWindow) {
-
-        // For freeform windows, we can't freeze the bounds at the moment because this would make
-        // the resizing unresponsive.
-        return appWindow != null && appWindow.mTask != null
-                && !appWindow.mTask.inFreeformWorkspace();
-    }
-
     @Override
     public int getDockedDividerInsetsLw() {
         return getDefaultDisplayContentLocked().getDockedDividerController().getContentInsets();
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 18aa186..8ada2f1 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1280,7 +1280,12 @@
                         "Check opening app=" + wtoken + ": allDrawn="
                         + wtoken.allDrawn + " startingDisplayed="
                         + wtoken.startingDisplayed + " startingMoved="
-                        + wtoken.startingMoved);
+                        + wtoken.startingMoved + " isRelaunching()="
+                        + wtoken.isRelaunching());
+
+                if (wtoken.isRelaunching()) {
+                    return false;
+                }
 
                 final boolean drawnBeforeRestoring = wtoken.allDrawn;
                 wtoken.restoreSavedSurfaces();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d979675..4831db5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -33,6 +33,7 @@
 import android.accounts.AccountManager;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
@@ -3642,7 +3643,7 @@
             if (count == 0 ||
                     count > admin.maximumFailedPasswordsForWipe ||
                     (count == admin.maximumFailedPasswordsForWipe &&
-                            mUserManager.getUserInfo(userId).isPrimary())) {
+                            getUserInfo(userId).isPrimary())) {
                 count = admin.maximumFailedPasswordsForWipe;
                 strictestAdmin = admin;
             }
@@ -3650,6 +3651,15 @@
         return strictestAdmin;
     }
 
+    private UserInfo getUserInfo(@UserIdInt int userId) {
+        final long token = mInjector.binderClearCallingIdentity();
+        try {
+            return mUserManager.getUserInfo(userId);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(token);
+        }
+    }
+
     @Override
     public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
         if (!mHasFeature) {
@@ -5913,7 +5923,7 @@
      * - adb if there are not accounts.
      */
     private void enforceCanSetProfileOwnerLocked(int userHandle) {
-        UserInfo info = mUserManager.getUserInfo(userHandle);
+        UserInfo info = getUserInfo(userHandle);
         if (info == null) {
             // User doesn't exist.
             throw new IllegalArgumentException(
@@ -6065,12 +6075,7 @@
     }
 
     private boolean isManagedProfile(int userHandle) {
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            return mUserManager.getUserInfo(userHandle).isManagedProfile();
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+        return getUserInfo(userHandle).isManagedProfile();
     }
 
     private void enableIfNecessary(String packageName, int userId) {
@@ -6409,7 +6414,7 @@
         try {
             // If we have an enabled packages list for a managed profile the packages
             // we should check are installed for the parent user.
-            UserInfo user = mUserManager.getUserInfo(userIdToCheck);
+            UserInfo user = getUserInfo(userIdToCheck);
             if (user.isManagedProfile()) {
                 userIdToCheck = user.profileGroupId;
             }
@@ -6455,7 +6460,7 @@
             List<AccessibilityServiceInfo> enabledServices = null;
             long id = mInjector.binderClearCallingIdentity();
             try {
-                UserInfo user = mUserManager.getUserInfo(userId);
+                UserInfo user = getUserInfo(userId);
                 if (user.isManagedProfile()) {
                     userId = user.profileGroupId;
                 }
@@ -6537,7 +6542,7 @@
             if (result != null) {
                 long id = mInjector.binderClearCallingIdentity();
                 try {
-                    UserInfo user = mUserManager.getUserInfo(userId);
+                    UserInfo user = getUserInfo(userId);
                     if (user.isManagedProfile()) {
                         userId = user.profileGroupId;
                     }
@@ -6591,7 +6596,7 @@
         long token = mInjector.binderClearCallingIdentity();
         try {
             UserInfo currentUser;
-            UserInfo callingUser = mUserManager.getUserInfo(callingUserId);
+            UserInfo callingUser = getUserInfo(callingUserId);
             try {
                 currentUser = mInjector.getIActivityManager().getCurrentUser();
             } catch (RemoteException e) {
@@ -8188,10 +8193,10 @@
     }
 
     @Override
-    public String getWifiMacAddress() {
+    public String getWifiMacAddress(ComponentName admin) {
         // Make sure caller has DO.
         synchronized (this) {
-            getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
         }
 
         final long ident = mInjector.binderClearCallingIdentity();
@@ -8223,13 +8228,7 @@
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         }
         final int callingUserId = mInjector.userHandleGetCallingUserId();
-        final UserInfo user;
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            user = mUserManager.getUserInfo(callingUserId);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+        final UserInfo user = getUserInfo(callingUserId);
         return user != null && user.isManagedProfile();
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 68fd0f6..b316cbd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -494,7 +494,6 @@
         abstract void writeInner(XmlSerializer out) throws IOException;
 
         abstract boolean readInner(XmlPullParser parser, int depth, String tag);
-
     }
 
     private class DeviceOwnerReadWriter extends FileReadWriter {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a2d5259..59f8284a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -83,6 +83,7 @@
 import com.android.server.pm.LauncherAppsService;
 import com.android.server.pm.OtaDexoptService;
 import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.ShortcutService;
 import com.android.server.pm.UserManagerService;
 import com.android.server.power.PowerManagerService;
 import com.android.server.power.ShutdownThread;
@@ -725,6 +726,9 @@
                 // Always start the Device Policy Manager, so that the API is compatible with
                 // API8.
                 mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+
+// TODO is this a good place?
+                mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
             }
 
             if (!disableSystemUI) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 3ae1072..23f186c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -106,6 +106,10 @@
 
         <service android:name="com.android.server.job.MockPriorityJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" />
+
+        <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity" />
+        <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity2" />
+        <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity3" />
     </application>
 
     <instrumentation
diff --git a/services/tests/servicestests/res/drawable/icon1.png b/services/tests/servicestests/res/drawable/icon1.png
new file mode 100644
index 0000000..64eb294
--- /dev/null
+++ b/services/tests/servicestests/res/drawable/icon1.png
Binary files differ
diff --git a/services/tests/servicestests/res/drawable/icon2.png b/services/tests/servicestests/res/drawable/icon2.png
new file mode 100644
index 0000000..7502484
--- /dev/null
+++ b/services/tests/servicestests/res/drawable/icon2.png
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 8c47087..e897e3d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1444,10 +1444,10 @@
 
         // Test 1. Caller doesn't have DO or DA.
         try {
-            dpm.getWifiMacAddress();
+            dpm.getWifiMacAddress(admin1);
             fail();
         } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("No active admin owned", e.getMessage());
+            MoreAsserts.assertContainsRegex("No active admin", e.getMessage());
         }
 
         // DO needs to be an DA.
@@ -1456,19 +1456,19 @@
 
         // Test 2. Caller has DA, but not DO.
         try {
-            dpm.getWifiMacAddress();
+            dpm.getWifiMacAddress(admin1);
             fail();
         } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("No active admin owned", e.getMessage());
+            MoreAsserts.assertContainsRegex("does not own the device", e.getMessage());
         }
 
         // Test 3. Caller has PO, but not DO.
         assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM));
         try {
-            dpm.getWifiMacAddress();
+            dpm.getWifiMacAddress(admin1);
             fail();
         } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("No active admin owned", e.getMessage());
+            MoreAsserts.assertContainsRegex("does not own the device", e.getMessage());
         }
 
         // Remove PO.
@@ -1478,17 +1478,17 @@
         assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM));
 
         // 4-1.  But no WifiInfo.
-        assertNull(dpm.getWifiMacAddress());
+        assertNull(dpm.getWifiMacAddress(admin1));
 
         // 4-2.  Returns WifiInfo, but with the default MAC.
         when(mContext.wifiManager.getConnectionInfo()).thenReturn(new WifiInfo());
-        assertNull(dpm.getWifiMacAddress());
+        assertNull(dpm.getWifiMacAddress(admin1));
 
         // 4-3. With a real MAC address.
         final WifiInfo wi = new WifiInfo();
         wi.setMacAddress("11:22:33:44:55:66");
         when(mContext.wifiManager.getConnectionInfo()).thenReturn(wi);
-        assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress());
+        assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1));
     }
 
     public void testRebootCanOnlyBeCalledByDeviceOwner() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
new file mode 100644
index 0000000..eb16a1d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
@@ -0,0 +1,44 @@
+
+/*
+ * 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.content.pm.ShortcutInfo;
+import android.test.AndroidTestCase;
+
+import com.android.server.testutis.TestUtils;
+
+/**
+ * Tests for {@link ShortcutInfo}.
+
+ m FrameworksServicesTests &&
+ adb install \
+   -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \
+   -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+
+ */
+public class ShortcutInfoTest extends AndroidTestCase {
+
+    public void testNoId() {
+        TestUtils.assertExpectException(
+                IllegalArgumentException.class,
+                "ID must be provided",
+                () -> new ShortcutInfo.Builder(mContext).build());
+    }
+
+    // TODO Add more tests.
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
new file mode 100644
index 0000000..1f805e9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -0,0 +1,1206 @@
+/*
+ * 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.NonNull;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.content.pm.ShortcutServiceInternal;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContext;
+import android.util.Log;
+
+import com.android.frameworks.servicestests.R;
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import org.junit.Assert;
+
+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.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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.ShortcutManagerTest \
+ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+public class ShortcutManagerTest extends AndroidTestCase {
+    private 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.
+     */
+    private static final boolean ENABLE_DUMP = true; // DO NOT SUBMIT WITH true
+
+    /** Context used in the client side */
+    private final class ClientContext extends MockContext {
+        @Override
+        public String getPackageName() {
+            return mInjectedClientPackage;
+        }
+    }
+
+    /** Context used in the service side */
+    private final class ServiceContext extends MockContext {
+    }
+
+    /** ShortcutService with injection override methods. */
+    private final class ShortcutServiceTestable extends ShortcutService {
+        public ShortcutServiceTestable(Context context) {
+            super(context);
+
+        }
+
+        @Override
+        void injectLoadConfigurationLocked() {
+            setResetIntervalForTest(INTERVAL);
+            setMaxDynamicShortcutsForTest(MAX_SHORTCUTS);
+            setMaxDailyUpdatesForTest(MAX_DAILY_UPDATES);
+        }
+
+        @Override
+        long injectCurrentTimeMillis() {
+            return mInjectedCurrentTimeLillis;
+        }
+
+        @Override
+        int injectBinderCallingUid() {
+            return mInjectedCallingUid;
+        }
+
+        @Override
+        int injectGetPackageUid(String packageName) {
+            Integer uid = mInjectedPackageUidMap.get(packageName);
+            return uid != null ? uid : -1;
+        }
+
+        @Override
+        File injectSystemDataPath() {
+            return new File(mInjectedFilePathRoot, "system");
+        }
+
+        @Override
+        File injectUserDataPath(@UserIdInt int userId) {
+            return new File(mInjectedFilePathRoot, "user-" + userId);
+        }
+    }
+
+    /** ShortcutManager with injection override methods. */
+    private final class ShortcutManagerTestable extends ShortcutManager {
+        public ShortcutManagerTestable(Context context, ShortcutServiceTestable service) {
+            super(context, service);
+        }
+
+        @Override
+        protected int injectMyUserId() {
+            return UserHandle.getUserId(mInjectedCallingUid);
+        }
+    }
+
+
+    public static class ShortcutActivity extends Activity {
+    }
+
+    public static class ShortcutActivity2 extends Activity {
+    }
+
+    public static class ShortcutActivity3 extends Activity {
+    }
+
+    private ServiceContext mServiceContext;
+    private ClientContext mClientContext;
+
+    private ShortcutServiceTestable mService;
+    private ShortcutManagerTestable mManager;
+    private ShortcutServiceInternal mInternal;
+
+    private File mInjectedFilePathRoot;
+
+    private long mInjectedCurrentTimeLillis;
+
+    private int mInjectedCallingUid;
+    private String mInjectedClientPackage;
+
+    private Map<String, Integer> mInjectedPackageUidMap;
+
+    private static final String CALLING_PACKAGE_1 = "com.android.test.1";
+    private static final int CALLING_UID_1 = 10001;
+
+    private static final String CALLING_PACKAGE_2 = "com.android.test.2";
+    private static final int CALLING_UID_2 = 10002;
+
+    private static final String CALLING_PACKAGE_3 = "com.android.test.3";
+    private static final int CALLING_UID_3 = 10003;
+
+    private static final String LAUNCHER_1 = "com.android.launcher.1";
+    private static final int LAUNCHER_UID_1 = 10011;
+
+    private static final String LAUNCHER_2 = "com.android.launcher.2";
+    private static final int LAUNCHER_UID_2 = 10012;
+
+    private static final long START_TIME = 1234560000000L;
+
+    private static final long INTERVAL = 10000;
+
+    private static final int MAX_SHORTCUTS = 5;
+
+    private static final int MAX_DAILY_UPDATES = 3;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mServiceContext = new ServiceContext();
+        mClientContext = new ClientContext();
+
+        // Prepare injection values.
+
+        mInjectedCurrentTimeLillis = START_TIME;
+
+        mInjectedPackageUidMap = new HashMap<>();
+        mInjectedPackageUidMap.put(CALLING_PACKAGE_1, CALLING_UID_1);
+        mInjectedPackageUidMap.put(CALLING_PACKAGE_2, CALLING_UID_2);
+        mInjectedPackageUidMap.put(CALLING_PACKAGE_3, CALLING_UID_3);
+        mInjectedPackageUidMap.put(LAUNCHER_1, LAUNCHER_UID_1);
+        mInjectedPackageUidMap.put(LAUNCHER_2, LAUNCHER_UID_2);
+
+        mInjectedFilePathRoot = new File(getContext().getCacheDir(), "test-files");
+
+        // Empty the data directory.
+        if (mInjectedFilePathRoot.exists()) {
+            Assert.assertTrue("failed to delete dir",
+                    FileUtils.deleteContents(mInjectedFilePathRoot));
+        }
+        mInjectedFilePathRoot.mkdirs();
+
+        initService();
+        setCaller(CALLING_PACKAGE_1);
+    }
+
+    /** (Re-) init the manager and the service. */
+    private void initService() {
+        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+
+        // Instantiate targets.
+        mService = new ShortcutServiceTestable(mServiceContext);
+        mManager = new ShortcutManagerTestable(mClientContext, mService);
+
+        mInternal = LocalServices.getService(ShortcutServiceInternal.class);
+
+        // Load the setting file.
+        mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY);
+    }
+
+    /** Replace the current calling package */
+    private void setCaller(String packageName) {
+        mInjectedClientPackage = packageName;
+        mInjectedCallingUid = Preconditions.checkNotNull(mInjectedPackageUidMap.get(packageName));
+    }
+
+    private String getCallingPackage() {
+        return mInjectedClientPackage;
+    }
+
+    private int getCallingUserId() {
+        return UserHandle.getUserId(mInjectedCallingUid);
+    }
+
+    /** For debugging */
+    private void dumpsysOnLogcat() {
+        if (!ENABLE_DUMP) return;
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        final PrintWriter pw = new PrintWriter(out);
+        mService.dumpInner(pw);
+        pw.close();
+
+        Log.e(TAG, "Dumping ShortcutService:");
+        for (String line : out.toString().split("\n")) {
+            Log.e(TAG, line);
+        }
+    }
+
+    /**
+     * For debugging, dump arbitrary file on logcat.
+     */
+    private void dumpFileOnLogcat(String path) {
+        if (!ENABLE_DUMP) return;
+
+        Log.i(TAG, "Dumping file: " + path);
+        final StringBuilder sb = new StringBuilder();
+        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                Log.i(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.
+     */
+    private void dumpBaseStateFile() {
+        dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
+                + "/system/" + ShortcutService.FILENAME_BASE_STATE);
+    }
+
+    /**
+     * For debugging, dump per-user state file on logcat.
+     */
+    private void dumpUserFile(int userId) {
+        dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
+                + "/user-" + userId
+                + "/" + ShortcutService.FILENAME_USER_PACKAGES);
+    }
+
+    private static Bundle makeBundle(Object... keysAndValues) {
+        Preconditions.checkState((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;
+    }
+
+    /**
+     * Make a shortcut with an ID.
+     */
+    private ShortcutInfo makeShortcut(String id) {
+        return makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
+    }
+
+    /**
+     * Make a shortcut with an ID and timestamp.
+     */
+    private ShortcutInfo makeShortcutWithTimestamp(String id, long timestamp) {
+        final ShortcutInfo s = makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
+        s.setTimestamp(timestamp);
+        return s;
+    }
+
+    /**
+     * Make multiple shortcuts with IDs.
+     */
+    private List<ShortcutInfo> makeShortcuts(String... ids) {
+        final ArrayList<ShortcutInfo> ret = new ArrayList();
+        for (String id : ids) {
+            ret.add(makeShortcut(id));
+        }
+        return ret;
+    }
+
+    /**
+     * Make a shortcut with details.
+     */
+    private ShortcutInfo makeShortcut(String id, String title, ComponentName activity,
+            Icon icon, Intent intent, int weight) {
+        final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext)
+                .setId(id)
+                .setTitle(title)
+                .setWeight(weight)
+                .setIntent(intent);
+        if (icon != null) {
+            b.setIcon(icon);
+        }
+        if (activity != null) {
+            b.setActivityComponent(activity);
+        }
+        final ShortcutInfo s = b.build();
+
+        s.setTimestamp(mInjectedCurrentTimeLillis); // HACK
+
+        return s;
+    }
+
+    /**
+     * Make an intent.
+     */
+    private 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
+    private ComponentName makeComponent(Class<?> clazz) {
+        return new ComponentName(mClientContext, clazz);
+    }
+
+    @NonNull
+    private 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;
+    }
+
+    private void assertResetTimes(long expectedLastResetTime, long expectedNextResetTime) {
+        assertEquals(expectedLastResetTime, mService.getLastResetTimeLocked());
+        assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked());
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertShortcutIds(@NonNull List<ShortcutInfo> actualShortcuts,
+            String... expectedIds) {
+        final HashSet<String> expected = new HashSet<>(Arrays.asList(expectedIds));
+        final HashSet<String> actual = new HashSet<>();
+        for (ShortcutInfo s : actualShortcuts) {
+            actual.add(s.getId());
+        }
+
+        // Compare the sets.
+        assertEquals(expected, actual);
+        return actualShortcuts;
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllHaveIntents(
+            @NonNull List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNotNull("ID " + s.getId(), s.getIntent());
+        }
+        return actualShortcuts;
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllNotHaveIntents(
+            @NonNull List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNull("ID " + s.getId(), s.getIntent());
+        }
+        return actualShortcuts;
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllHaveTitle(
+            @NonNull List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNotNull("ID " + s.getId(), s.getTitle());
+        }
+        return actualShortcuts;
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllNotHaveTitle(
+            @NonNull List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertNull("ID " + s.getId(), s.getTitle());
+        }
+        return actualShortcuts;
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts,
+            int shortcutFlags) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.hasFlags(shortcutFlags));
+        }
+        return actualShortcuts;
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllDynamic(@NonNull List<ShortcutInfo> actualShortcuts) {
+        return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_DYNAMIC);
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllPinned(@NonNull List<ShortcutInfo> actualShortcuts) {
+        return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_PINNED);
+    }
+
+    @NonNull
+    private List<ShortcutInfo> assertAllDynamicOrPinned(
+            @NonNull List<ShortcutInfo> actualShortcuts) {
+        for (ShortcutInfo s : actualShortcuts) {
+            assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned());
+        }
+        return actualShortcuts;
+    }
+
+    /**
+     * Test for the first launch path, no settings file available.
+     */
+    public void testFirstInitialize() {
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+    }
+
+    /**
+     * Test for {@link ShortcutService#updateTimes()}
+     */
+    public void testUpdateAndGetNextResetTimeLocked() {
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+
+        // Advance clock.
+        mInjectedCurrentTimeLillis += 100;
+
+        // Shouldn't have changed.
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+
+        // Advance clock, almost the reset time.
+        mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1;
+
+        // Shouldn't have changed.
+        assertResetTimes(START_TIME, START_TIME + INTERVAL);
+
+        // Advance clock.
+        mInjectedCurrentTimeLillis += 1;
+
+        assertResetTimes(START_TIME + INTERVAL, START_TIME + 2 * INTERVAL);
+
+        // Advance further; 4 days since start.
+        mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL + 50;
+
+        assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
+    }
+
+    /**
+     * Test for the restoration from saved file.
+     */
+    public void testInitializeFromSavedFile() {
+
+        mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL + 50;
+        assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
+
+        mService.saveBaseStateLocked();
+
+        dumpBaseStateFile();
+
+        // 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.
+    }
+
+    // === Test for app side APIs ===
+
+    /** Test for {@link android.content.pm.ShortcutManager#getMaxDynamicShortcutCount()} */
+    public void testGetMaxDynamicShortcutCount() {
+        assertEquals(MAX_SHORTCUTS, mManager.getMaxDynamicShortcutCount());
+    }
+
+    /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */
+    public void testGetRemainingCallCount() {
+        assertEquals(MAX_DAILY_UPDATES, mManager.getRemainingCallCount());
+    }
+
+    /** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */
+    public void testGetRateLimitResetTime() {
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+
+        mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL + 50;
+
+        assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime());
+    }
+
+    public void testSetDynamicShortcuts() {
+        final Icon icon1 = Icon.createWithResource(mContext, R.drawable.icon1);
+        final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                mContext.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(Arrays.asList(si1, si2)));
+        assertEquals(2, mManager.getDynamicShortcuts().size());
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        // TODO: Check fields
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(1, mManager.getDynamicShortcuts().size());
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList()));
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        dumpsysOnLogcat();
+
+        mInjectedCurrentTimeLillis++; // Need to advance the clock for reset to work.
+        mService.resetThrottlingInner();
+
+        dumpsysOnLogcat();
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2, si3)));
+        assertEquals(2, mManager.getDynamicShortcuts().size());
+
+        // TODO Check max number
+    }
+
+    public void testAddDynamicShortcuts() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+        final ShortcutInfo si2 = makeShortcut("shortcut2");
+        final ShortcutInfo si3 = makeShortcut("shortcut3");
+
+        assertEquals(3, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+        assertEquals(1, mManager.getDynamicShortcuts().size());
+
+        assertTrue(mManager.addDynamicShortcut(si2));
+        assertEquals(1, mManager.getRemainingCallCount());
+        assertEquals(2, mManager.getDynamicShortcuts().size());
+
+        // Add with the same ID
+        assertTrue(mManager.addDynamicShortcut(makeShortcut("shortcut1")));
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertEquals(2, mManager.getDynamicShortcuts().size()); // Still 2
+
+        // TODO Check max number
+
+        // TODO Check fields.
+    }
+
+    public void testDeleteDynamicShortcut() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+        final ShortcutInfo si2 = makeShortcut("shortcut2");
+        final ShortcutInfo si3 = makeShortcut("shortcut3");
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3)));
+        assertEquals(3, mManager.getDynamicShortcuts().size());
+
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mManager.deleteDynamicShortcut("shortcut1");
+        assertEquals(2, mManager.getDynamicShortcuts().size());
+
+        mManager.deleteDynamicShortcut("shortcut1");
+        assertEquals(2, mManager.getDynamicShortcuts().size());
+
+        mManager.deleteDynamicShortcut("shortcutXXX");
+        assertEquals(2, mManager.getDynamicShortcuts().size());
+
+        mManager.deleteDynamicShortcut("shortcut2");
+        assertEquals(1, mManager.getDynamicShortcuts().size());
+
+        mManager.deleteDynamicShortcut("shortcut3");
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+
+        // Still 2 calls left.
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        // TODO Make sure pinned shortcuts won't be deleted.
+    }
+
+    public void testDeleteAllDynamicShortcuts() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+        final ShortcutInfo si2 = makeShortcut("shortcut2");
+        final ShortcutInfo si3 = makeShortcut("shortcut3");
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3)));
+        assertEquals(3, mManager.getDynamicShortcuts().size());
+
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mManager.deleteAllDynamicShortcuts();
+        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(Arrays.asList(si1, si2, si3)));
+        assertEquals(3, mManager.getDynamicShortcuts().size());
+
+        // Still 1 call left
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        // TODO Make sure pinned shortcuts won't be deleted.
+    }
+
+    public void testThrottling() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // Reached the max
+
+        mInjectedCurrentTimeLillis++;
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // Still throttled
+        mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1;
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // Now it should work.
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // 4 days later...
+        mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        // Make sure getRemainingCallCount() itself gets reset withou calling setDynamicShortcuts().
+        mInjectedCurrentTimeLillis = START_TIME + 8 * INTERVAL;
+        assertEquals(3, mManager.getRemainingCallCount());
+    }
+
+    public void testThrottling_perPackage() {
+        final ShortcutInfo si1 = makeShortcut("shortcut1");
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // Reached the max
+
+        mInjectedCurrentTimeLillis++;
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(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(Arrays.asList(si2)));
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+        assertEquals(1, mManager.getRemainingCallCount());
+
+        // Back to the original caller, still throttled.
+        mInjectedClientPackage = CALLING_PACKAGE_1;
+        mInjectedCallingUid = CALLING_UID_1;
+
+        mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1;
+        assertEquals(0, mManager.getRemainingCallCount());
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertEquals(0, mManager.getRemainingCallCount());
+
+        // Now it should work.
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+
+        mInjectedCurrentTimeLillis++;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+
+        mInjectedCurrentTimeLillis++;
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+
+        mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL;
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+
+        mInjectedClientPackage = CALLING_PACKAGE_2;
+        mInjectedCallingUid = CALLING_UID_2;
+
+        assertEquals(3, mManager.getRemainingCallCount());
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+    }
+
+    // TODO: updateShortcuts()
+    // TODO: getPinnedShortcuts()
+
+    // === Test for launcher side APIs ===
+
+    public void testGetShortcuts() {
+
+        // Set up shortcuts.
+
+        setCaller(CALLING_PACKAGE_1);
+        final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 5000);
+        final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 1000);
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2)));
+
+        setCaller(CALLING_PACKAGE_2);
+        final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
+        final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000);
+        final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500);
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2, s2_3, s2_4)));
+
+        setCaller(CALLING_PACKAGE_3);
+        final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", 5000);
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s3_2)));
+
+        setCaller(LAUNCHER_1);
+
+        // Get dynamic
+        assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_1,
+                        /* activity =*/ null,
+                    ShortcutQuery.FLAG_GET_DYNAMIC, getCallingUserId()),
+                "s1", "s2"))));
+
+        // Get pinned
+        assertShortcutIds(
+                mInternal.getShortcuts(getCallingPackage(),  /* time =*/ 0, CALLING_PACKAGE_1,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_PINNED, getCallingUserId())
+                /* none */);
+
+        // Get both, with timestamp
+        assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                mInternal.getShortcuts(getCallingPackage(),  /* time =*/ 1000, CALLING_PACKAGE_2,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC,
+                        getCallingUserId()),
+                "s2", "s3"))));
+
+        // FLAG_GET_KEY_FIELDS_ONLY
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                mInternal.getShortcuts(getCallingPackage(), /* time =*/ 1000, CALLING_PACKAGE_2,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY,
+                        getCallingUserId()),
+                "s2", "s3"))));
+
+        // Pin some shortcuts.
+        mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_2,
+                Arrays.asList("s3", "s4"), getCallingUserId());
+
+        // Pinned ones only
+        assertAllPinned(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                mInternal.getShortcuts(getCallingPackage(), /* time =*/ 1000, CALLING_PACKAGE_2,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_PINNED,
+                        getCallingUserId()),
+                "s3"))));
+
+        // All packages.
+        assertShortcutIds(
+                mInternal.getShortcuts(getCallingPackage(),
+                        /* time =*/ 5000, /* package= */ null,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED,
+                        getCallingUserId()),
+                "s1", "s3");
+
+        // TODO More tests: pinned but dynamic, filter by activity
+    }
+
+    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(Arrays.asList(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(Arrays.asList(s2_1)));
+        dumpsysOnLogcat();
+
+        // Pin some.
+        setCaller(LAUNCHER_1);
+
+        mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_1,
+                Arrays.asList("s2"), getCallingUserId());
+
+        dumpsysOnLogcat();
+
+        // Delete some.
+        setCaller(CALLING_PACKAGE_1);
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+        mManager.deleteDynamicShortcut("s2");
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+
+        dumpsysOnLogcat();
+
+        setCaller(LAUNCHER_1);
+        List<ShortcutInfo> list;
+
+        // Note we don't guarantee the orders.
+        list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
+                mInternal.getShortcutInfo(getCallingPackage(), CALLING_PACKAGE_1,
+                Arrays.asList("s2", "s1", "s3", null), getCallingUserId()))),
+                "s1", "s2");
+        assertEquals("Title 1", findById(list, "s1").getTitle());
+        assertEquals("Title 2", findById(list, "s2").getTitle());
+
+        assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
+                mInternal.getShortcutInfo(getCallingPackage(), CALLING_PACKAGE_1,
+                        Arrays.asList("s3"), getCallingUserId())))
+                /* none */);
+
+        list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
+                mInternal.getShortcutInfo(getCallingPackage(), CALLING_PACKAGE_2,
+                        Arrays.asList("s1", "s2", "s3"), getCallingUserId()))),
+                "s1");
+        assertEquals("ABC", findById(list, "s1").getTitle());
+    }
+
+    public void testPinShortcutAndGetPinnedShortcuts() {
+        // Create some shortcuts.
+        setCaller(CALLING_PACKAGE_1);
+        final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
+        final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2)));
+
+        setCaller(CALLING_PACKAGE_2);
+        final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
+        final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000);
+        final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500);
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2, s2_3, s2_4)));
+
+        setCaller(CALLING_PACKAGE_3);
+        final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s2", 1000);
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2)));
+
+        // Pin some.
+        setCaller(LAUNCHER_1);
+
+        mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_1,
+                Arrays.asList("s2", "s3"), getCallingUserId());
+
+        mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_2,
+                Arrays.asList("s3", "s4", "s5"), getCallingUserId());
+
+        mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_3,
+                Arrays.asList("s3"), getCallingUserId());  // Note ID doesn't exist
+
+        // Delete some.
+        setCaller(CALLING_PACKAGE_1);
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+        mManager.deleteDynamicShortcut("s2");
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+
+        setCaller(CALLING_PACKAGE_2);
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+        mManager.deleteDynamicShortcut("s3");
+        assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+
+        setCaller(CALLING_PACKAGE_3);
+        assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+        mManager.deleteDynamicShortcut("s2");
+        assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+
+        // Get pinned shortcuts from launcher
+        setCaller(LAUNCHER_1);
+
+        // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
+        assertShortcutIds(assertAllPinned(
+                mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_1,
+                /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED, getCallingUserId())),
+                "s2");
+
+        assertShortcutIds(assertAllPinned(
+                mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_2,
+                /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED, getCallingUserId())),
+                "s3", "s4");
+
+        assertShortcutIds(assertAllPinned(
+                mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_3,
+                /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED, getCallingUserId()))
+                /* none */);
+    }
+
+    public void testCreateShortcutIntent() {
+        // 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(Arrays.asList(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(Arrays.asList(s2_1)));
+
+        // Pin all.
+        setCaller(LAUNCHER_1);
+        mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_1,
+                Arrays.asList("s1", "s2"), getCallingUserId());
+
+        mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_2,
+                Arrays.asList("s1"), getCallingUserId());
+
+        // Just to make it complicated, delete some.
+        setCaller(CALLING_PACKAGE_1);
+        mManager.deleteDynamicShortcut("s2");
+
+        // intent and check.
+        setCaller(LAUNCHER_1);
+        Intent intent;
+        intent = mInternal.createShortcutIntent(getCallingPackage(),
+                CALLING_PACKAGE_1, "s1", getCallingUserId());
+        assertEquals(ShortcutActivity2.class.getName(), intent.getComponent().getClassName());
+
+        intent = mInternal.createShortcutIntent(getCallingPackage(),
+                CALLING_PACKAGE_1, "s2", getCallingUserId());
+        assertEquals(ShortcutActivity3.class.getName(), intent.getComponent().getClassName());
+
+        intent = mInternal.createShortcutIntent(getCallingPackage(),
+                CALLING_PACKAGE_2, "s1", getCallingUserId());
+        assertEquals(ShortcutActivity.class.getName(), intent.getComponent().getClassName());
+
+        // TODO Check extra, etc
+    }
+
+    // === Test for persisting ===
+
+    public void testSaveAndLoadUser_empty() {
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList()));
+
+        Log.i(TAG, "Saved state");
+        dumpsysOnLogcat();
+        dumpUserFile(0);
+
+        // Restore.
+        initService();
+
+        assertEquals(0, mManager.getDynamicShortcuts().size());
+    }
+
+    /**
+     * Try save and load, also stop/start the user.
+     */
+    public void testSaveAndLoadUser() {
+        // First, create some shortcuts and save.
+        final Icon icon1 = Icon.createWithResource(mContext, R.drawable.icon1);
+        final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                mContext.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);
+
+        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        Log.i(TAG, "Saved state");
+        dumpsysOnLogcat();
+        dumpUserFile(0);
+
+        // Restore.
+        initService();
+
+        // Before the load, the map should be empty.
+        assertEquals(0, mService.getShortcutsForTest().size());
+
+        // this will pre-load the per-user info.
+        mService.onStartUserLocked(UserHandle.USER_SYSTEM);
+
+        // Now it's loaded.
+        assertEquals(1, mService.getShortcutsForTest().size());
+
+        // Start another user
+        mService.onStartUserLocked(10);
+
+        // Now the size is 2.
+        assertEquals(2, mService.getShortcutsForTest().size());
+
+        Log.i(TAG, "Dumping the new instance");
+
+        List<ShortcutInfo> loaded = mManager.getDynamicShortcuts();
+
+        Log.i(TAG, "Loaded state");
+        dumpsysOnLogcat();
+
+        assertEquals(2, loaded.size());
+
+        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+        assertEquals(2, mManager.getRemainingCallCount());
+
+        // Try stopping the user
+        mService.onCleanupUserInner(UserHandle.USER_SYSTEM);
+
+        // Now it's unloaded.
+        assertEquals(1, mService.getShortcutsForTest().size());
+
+        // TODO Check all other fields
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
new file mode 100644
index 0000000..52e8f37
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.testutis;
+
+import android.test.MoreAsserts;
+
+import junit.framework.Assert;
+
+public class TestUtils {
+    private TestUtils() {
+    }
+
+    public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
+            Runnable r) {
+        assertExpectException(expectedExceptionType, null, r);
+    }
+
+    public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
+            String expectedExceptionMessageRegex, Runnable r) {
+        try {
+            r.run();
+            Assert.fail("Expected exception type " + expectedExceptionType.getClass().getName()
+                    + " was not thrown");
+        } catch (Throwable e) {
+            Assert.assertTrue(
+                    "Expected exception type was " + expectedExceptionType.getClass().getName()
+                    + " but caught " + e.getClass().getName(),
+                    expectedExceptionType.isAssignableFrom(e.getClass()));
+            if (expectedExceptionMessageRegex != null) {
+                MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
+            }
+        }
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index e05f00d..40687b0 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -171,7 +171,7 @@
 
             // Fetch a ModelData instance from the hash map. Creates a new one if none
             // exists.
-            ModelData modelData = getOrCreateGenericModelData(modelId);
+            ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
 
             IRecognitionStatusCallback oldCallback = modelData.getCallback();
             if (oldCallback != null) {
@@ -373,7 +373,7 @@
             // Also clear the internal state once the recognition has been stopped.
             modelData.setLoaded();
             modelData.clearCallback();
-            if (!computeRecognitionRunning()) {
+            if (!computeRecognitionRunningLocked()) {
                 internalClearGlobalStateLocked();
             }
             return status;
@@ -505,12 +505,12 @@
         if (modelId == null || mModule == null) {
             return STATUS_ERROR;
         }
-        ModelData modelData = mGenericModelDataMap.get(modelId);
-        if (modelData == null) {
-            Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + modelId);
-            return STATUS_ERROR;
-        }
         synchronized (mLock) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            if (modelData == null) {
+                Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + modelId);
+                return STATUS_ERROR;
+            }
             if (!modelData.isModelLoaded()) {
                 // Nothing to do here.
                 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
@@ -530,7 +530,7 @@
                 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
             }
             mGenericModelDataMap.remove(modelId);
-            if (DBG) dumpGenericModelState();
+            if (DBG) dumpGenericModelStateLocked();
             return status;
         }
     }
@@ -580,7 +580,7 @@
         if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
             return;
         }
-        ModelData model = getModelDataFor(event.soundModelHandle);
+        ModelData model = getModelDataForLocked(event.soundModelHandle);
         if (model == null) {
             Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
                     event.soundModelHandle);
@@ -919,7 +919,7 @@
         mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
     }
 
-    private ModelData getOrCreateGenericModelData(UUID modelId) {
+    private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
         ModelData modelData = mGenericModelDataMap.get(modelId);
         if (modelData == null) {
             modelData = new ModelData(modelId);
@@ -932,7 +932,7 @@
     // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
     // iterate through to find the right object (since we don't expect 100s of models
     // to be stored).
-    private ModelData getModelDataFor(int modelHandle) {
+    private ModelData getModelDataForLocked(int modelHandle) {
         // Fetch ModelData object corresponding to the model handle.
         for (ModelData model : mGenericModelDataMap.values()) {
             if (model.getHandle() == modelHandle) {
@@ -988,7 +988,7 @@
                 }
             }
         }
-        if (DBG) dumpGenericModelState();
+        if (DBG) dumpGenericModelStateLocked();
         return status;
     }
 
@@ -1017,11 +1017,11 @@
                 }
             }
         }
-        if (DBG) dumpGenericModelState();
+        if (DBG) dumpGenericModelStateLocked();
         return status;
     }
 
-    private void dumpGenericModelState() {
+    private void dumpGenericModelStateLocked() {
         for (UUID modelId : mGenericModelDataMap.keySet()) {
             ModelData modelData = mGenericModelDataMap.get(modelId);
             Slog.i(TAG, "Model :" + modelData.toString());
@@ -1030,28 +1030,24 @@
 
     // Computes whether we have any recognition running at all (voice or generic). Sets
     // the mRecognitionRunning variable with the result.
-    private boolean computeRecognitionRunning() {
-        synchronized (mLock) {
-            if (mModuleProperties == null || mModule == null) {
-                mRecognitionRunning = false;
-                return mRecognitionRunning;
-            }
-            if (mKeyphraseListener != null &&
-                    mKeyphraseStarted &&
-                    mCurrentKeyphraseModelHandle != INVALID_VALUE &&
-                    mCurrentSoundModel != null) {
+    private boolean computeRecognitionRunningLocked() {
+        if (mModuleProperties == null || mModule == null) {
+            mRecognitionRunning = false;
+            return mRecognitionRunning;
+        }
+        if (mKeyphraseListener != null && mKeyphraseStarted &&
+            mCurrentKeyphraseModelHandle != INVALID_VALUE && mCurrentSoundModel != null) {
+            mRecognitionRunning = true;
+            return mRecognitionRunning;
+        }
+        for (UUID modelId : mGenericModelDataMap.keySet()) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            if (modelData.isModelStarted()) {
                 mRecognitionRunning = true;
                 return mRecognitionRunning;
             }
-            for (UUID modelId : mGenericModelDataMap.keySet()) {
-                ModelData modelData = mGenericModelDataMap.get(modelId);
-                if (modelData.isModelStarted()) {
-                    mRecognitionRunning = true;
-                    return mRecognitionRunning;
-                }
-            }
-            mRecognitionRunning = false;
         }
+        mRecognitionRunning = false;
         return mRecognitionRunning;
     }
 
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 4bed941..ed7351f8 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -160,7 +160,7 @@
         for (String pair : appNames) {
             String[] parts = pair.split("\\^");
             if (parts.length != 2) {
-                Log.e(TAG, "The apps key is incorectly formatted");
+                Log.e(TAG, "The apps key is incorrectly formatted");
                 fail();
             }
 
@@ -176,6 +176,10 @@
         }
     }
 
+    private boolean hasLeanback(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
     private void createMappings() {
         mNameToIntent = new LinkedHashMap<String, Intent>();
         mNameToProcess = new LinkedHashMap<String, String>();
@@ -183,9 +187,12 @@
         PackageManager pm = getInstrumentation().getContext()
                 .getPackageManager();
         Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
-        intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+        intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ?
+                Intent.CATEGORY_LEANBACK_LAUNCHER :
+                Intent.CATEGORY_LAUNCHER);
         List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
         resolveLoop(ris, intentToResolve, pm);
+        // For Wear
         intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE);
         ris = pm.queryIntentActivities(intentToResolve, 0);
         resolveLoop(ris, intentToResolve, pm);
@@ -232,7 +239,7 @@
         // report error if any of the following is true:
         // * launch thread is alive
         // * result is not null, but:
-        //   * result is not START_SUCESS
+        //   * result is not START_SUCCESS
         //   * or in case of no force stop, result is not TASK_TO_FRONT either
         if (t.isAlive() || (result != null
                 && ((result.result != ActivityManager.START_SUCCESS)
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_clamp.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_clamp.xml
new file mode 100644
index 0000000..6a24453
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_clamp.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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:angle="90"
+          android:startColor="?android:attr/colorPrimary"
+          android:endColor="?android:attr/colorControlActivated"
+          android:centerColor="#00ff0000"
+          android:startX="0"
+          android:startY="0"
+          android:endX="50"
+          android:endY="50"
+          android:type="linear"
+          android:tileMode="clamp">
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap_mirror.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap_mirror.xml
new file mode 100644
index 0000000..d342bca
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap_mirror.xml
@@ -0,0 +1,34 @@
+<?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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:angle="90"
+          android:startColor="?android:attr/colorPrimary"
+          android:endColor="?android:attr/colorControlActivated"
+          android:centerColor="#f00"
+          android:startX="0"
+          android:startY="0"
+          android:endX="50"
+          android:endY="50"
+          android:type="linear"
+          android:tileMode="mirror">
+    <item android:offset="0.1" android:color="?android:attr/colorPrimary"/>
+    <item android:offset="0.4" android:color="#f00"/>
+    <item android:offset="0.4" android:color="#fff"/>
+    <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/>
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_repeat.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_repeat.xml
new file mode 100644
index 0000000..afb45aa
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_repeat.xml
@@ -0,0 +1,33 @@
+<?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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:angle="90"
+          android:startColor="?android:attr/colorPrimary"
+          android:endColor="?android:attr/colorControlActivated"
+          android:centerColor="#f00"
+          android:startX="0"
+          android:startY="0"
+          android:endX="50"
+          android:endY="50"
+          android:type="linear"
+          android:tileMode="repeat">
+    <item android:offset="0.1" android:color="?android:attr/colorPrimary"/>
+    <item android:offset="0.4" android:color="#fff"/>
+    <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/>
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_clamp.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_clamp.xml
new file mode 100644
index 0000000..64b32f6
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_clamp.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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:centerColor="#ff0000"
+          android:endColor="?android:attr/colorControlActivated"
+          android:centerX="300"
+          android:centerY="300"
+          android:gradientRadius="50"
+          android:startColor="?android:attr/colorPrimary"
+          android:type="radial"
+          android:tileMode="clamp">
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml
index 51b0e17..c6cea7c 100644
--- a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-    <!--
+<!--
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_repeat.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_repeat.xml
new file mode 100644
index 0000000..fb4346a
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_repeat.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:centerColor="#ff0000"
+          android:endColor="#ff0000ff"
+          android:centerX="300"
+          android:centerY="300"
+          android:gradientRadius="50"
+          android:startColor="#ffffffff"
+          android:type="radial"
+          android:tileMode="repeat">
+    <item android:offset="0.1" android:color="?android:attr/colorPrimary"/>
+    <item android:offset="0.4" android:color="#fff"/>
+    <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/>
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml
index 8caa1b4..fefbe9f 100644
--- a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-    <!--
+<!--
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short_mirror.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short_mirror.xml
new file mode 100644
index 0000000..8b5ad7c
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short_mirror.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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:centerX="300"
+          android:centerY="300"
+          android:gradientRadius="50"
+          android:type="radial"
+          android:tileMode="mirror">
+    <item android:offset="0.1" android:color="?android:attr/colorPrimary"/>
+    <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/>
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_clamp.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_clamp.xml
new file mode 100644
index 0000000..80f39f3e
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_clamp.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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:centerColor="#ff0000"
+          android:endColor="#ff0000ff"
+          android:centerX="500"
+          android:centerY="500"
+          android:gradientRadius="10"
+          android:startColor="#ffffffff"
+          android:type="sweep"
+          android:tileMode="clamp">
+</gradient>
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long_mirror.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long_mirror.xml
new file mode 100644
index 0000000..0890bd6
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long_mirror.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:centerX="500"
+          android:centerY="500"
+          android:gradientRadius="10"
+          android:type="sweep"
+          android:tileMode="mirror">
+    <item android:offset="-0.3" android:color="#f00"/>
+    <item android:offset="0.1" android:color="?android:attr/colorPrimary"/>
+    <item android:offset="0.4" android:color="#0f0"/>
+    <item android:offset="0.6" android:color="#00f"/>
+    <item android:offset="0.7" android:color="?android:attr/colorControlActivated"/>
+    <item android:offset="1.5" android:color="#00f"/>
+</gradient>
diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_repeat.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_repeat.xml
new file mode 100644
index 0000000..2ec5014
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_repeat.xml
@@ -0,0 +1,31 @@
+<?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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:centerColor="#ff0000"
+          android:endColor="#ff0000ff"
+          android:centerX="500"
+          android:centerY="500"
+          android:gradientRadius="10"
+          android:startColor="#ffffffff"
+          android:type="sweep"
+          android:tileMode="repeat">
+    <item android:offset="0.1" android:color="?android:attr/colorPrimary"/>
+    <item android:offset="0.4" android:color="#fff"/>
+    <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/>
+</gradient>
diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_clamp.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_clamp.xml
new file mode 100644
index 0000000..3d746e7
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/stroke_gradient_clamp.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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:angle="90"
+          android:centerColor="#7f7f7f"
+          android:endColor="#ffffff"
+          android:startColor="#000000"
+          android:startX="0"
+          android:endX="50"
+          android:startY="0"
+          android:endY="0"
+          android:type="linear"
+          android:tileMode="clamp">
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha_mirror.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha_mirror.xml
new file mode 100644
index 0000000..352a2fd
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha_mirror.xml
@@ -0,0 +1,29 @@
+<?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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:startX="0"
+          android:endX="50"
+          android:startY="0"
+          android:endY="0"
+          android:type="linear"
+          android:tileMode="mirror">
+    <item android:offset="0.1" android:color="#f00"/>
+    <item android:offset="0.2" android:color="#2f0f"/>
+    <item android:offset="0.9" android:color="#f00f"/>
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_item_repeat.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_item_repeat.xml
new file mode 100644
index 0000000..42281d1
--- /dev/null
+++ b/tests/VectorDrawableTest/res/color/stroke_gradient_item_repeat.xml
@@ -0,0 +1,33 @@
+<?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.
+ */
+-->
+<gradient xmlns:android="http://schemas.android.com/apk/res/android"
+          android:angle="90"
+          android:centerColor="#7f7f7f"
+          android:endColor="#ffffff"
+          android:startColor="#000000"
+          android:startX="0"
+          android:endX="50"
+          android:startY="0"
+          android:endY="0"
+          android:type="linear"
+          android:tileMode="repeat">
+    <item android:offset="0.1" android:color="#f00"/>
+    <item android:offset="0.2" android:color="#f0f"/>
+    <item android:offset="0.9" android:color="#f00f"/>
+</gradient>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1_clamp.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1_clamp.xml
new file mode 100644
index 0000000..2fa440a
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1_clamp.xml
@@ -0,0 +1,91 @@
+<?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:height="64dp"
+        android:width="64dp"
+        android:viewportHeight="400"
+        android:viewportWidth="400" >
+
+<group android:name="backgroundGroup"
+       android:scaleX="0.5"
+       android:scaleY="0.5">
+    <path
+            android:name="background1"
+            android:fillColor="@color/fill_gradient_linear_clamp"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+    <path
+            android:name="background2"
+            android:fillColor="@color/fill_gradient_radial_clamp"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    <path
+            android:name="background3"
+            android:fillColor="@color/fill_gradient_sweep_clamp"
+            android:pathData="M 400,400 l 200,0 l 0, 200 l -200, 0 z" />
+</group>
+<group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0" >
+    <path
+            android:name="twoLines"
+            android:pathData="@string/twoLinePathData"
+            android:strokeColor="@color/stroke_gradient_clamp"
+            android:strokeWidth="20" />
+
+    <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0">
+        <path
+                android:name="twoLines1"
+                android:pathData="@string/twoLinePathData"
+                android:strokeColor="@color/stroke_gradient_clamp"
+                android:strokeWidth="20" />
+
+        <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0">
+            <group android:name="scaleGroup" >
+                <path
+                        android:name="twoLines3"
+                        android:pathData="@string/twoLinePathData"
+                        android:strokeColor="@color/stroke_gradient_clamp"
+                        android:strokeWidth="20" />
+            </group>
+        </group>
+
+        <group
+                android:name="translateGroupHalf"
+                android:translateX="65.0"
+                android:translateY="80.0">
+            <group android:name="scaleGroup" >
+                <path
+                        android:name="twoLines2"
+                        android:pathData="@string/twoLinePathData"
+                        android:fillColor="@color/fill_gradient_linear_clamp"
+                        android:strokeColor="@color/stroke_gradient_clamp"
+                        android:strokeWidth="20" />
+            </group>
+        </group>
+    </group>
+</group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2_repeat.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2_repeat.xml
new file mode 100644
index 0000000..5a43f80
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2_repeat.xml
@@ -0,0 +1,90 @@
+<?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:height="64dp"
+        android:width="64dp"
+        android:viewportHeight="400"
+        android:viewportWidth="400" >
+
+<group android:name="backgroundGroup"
+       android:scaleX="0.5"
+       android:scaleY="0.5">
+    <path
+            android:name="background1"
+            android:fillColor="@color/fill_gradient_linear_item_repeat"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+    <path
+            android:name="background2"
+            android:fillColor="@color/fill_gradient_radial_item_repeat"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    <path
+            android:name="background3"
+            android:fillColor="@color/fill_gradient_sweep_item_repeat"
+            android:pathData="M 400,400 l 200,0 l 0, 200 l -200, 0 z" />
+</group>
+<group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0" >
+    <path
+            android:name="twoLines"
+            android:pathData="@string/twoLinePathData"
+            android:strokeColor="@color/stroke_gradient_item_repeat"
+            android:strokeWidth="20" />
+
+    <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0">
+        <path
+                android:name="twoLines1"
+                android:pathData="@string/twoLinePathData"
+                android:strokeColor="@color/stroke_gradient_item_repeat"
+                android:strokeWidth="20" />
+
+        <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0">
+            <group android:name="scaleGroup" >
+                <path
+                        android:name="twoLines3"
+                        android:pathData="@string/twoLinePathData"
+                        android:strokeColor="@color/stroke_gradient_item_repeat"
+                        android:strokeWidth="20" />
+            </group>
+        </group>
+
+        <group
+                android:name="translateGroupHalf"
+                android:translateX="65.0"
+                android:translateY="80.0">
+            <group android:name="scaleGroup" >
+                <path
+                        android:name="twoLines2"
+                        android:pathData="@string/twoLinePathData"
+                        android:strokeColor="@color/stroke_gradient_item_repeat"
+                        android:strokeWidth="20" />
+            </group>
+        </group>
+    </group>
+</group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3_mirror.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3_mirror.xml
new file mode 100644
index 0000000..e8de7c2
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3_mirror.xml
@@ -0,0 +1,90 @@
+<?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:height="64dp"
+        android:width="64dp"
+        android:viewportHeight="400"
+        android:viewportWidth="400" >
+
+<group android:name="backgroundGroup"
+       android:scaleX="0.5"
+       android:scaleY="0.5">
+    <path
+            android:name="background1"
+            android:fillColor="@color/fill_gradient_linear_item_overlap_mirror"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+    <path
+            android:name="background2"
+            android:fillColor="@color/fill_gradient_radial_item_short_mirror"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    <path
+            android:name="background3"
+            android:fillColor="@color/fill_gradient_sweep_item_long_mirror"
+            android:pathData="M 400,400 l 200,0 l 0, 200 l -200, 0 z" />
+</group>
+<group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0" >
+    <path
+            android:name="twoLines"
+            android:pathData="@string/twoLinePathData"
+            android:strokeColor="@color/stroke_gradient_item_alpha_mirror"
+            android:strokeWidth="20" />
+
+    <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0">
+        <path
+                android:name="twoLines1"
+                android:pathData="@string/twoLinePathData"
+                android:strokeColor="@color/stroke_gradient_item_alpha_mirror"
+                android:strokeWidth="20" />
+
+        <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0">
+            <group android:name="scaleGroup" >
+                <path
+                        android:name="twoLines3"
+                        android:pathData="@string/twoLinePathData"
+                        android:strokeColor="@color/stroke_gradient_item_alpha_mirror"
+                        android:strokeWidth="20" />
+            </group>
+        </group>
+
+        <group
+                android:name="translateGroupHalf"
+                android:translateX="65.0"
+                android:translateY="80.0">
+            <group android:name="scaleGroup" >
+                <path
+                        android:name="twoLines2"
+                        android:pathData="@string/twoLinePathData"
+                        android:strokeColor="@color/stroke_gradient_item_alpha"
+                        android:strokeWidth="20" />
+            </group>
+        </group>
+    </group>
+</group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
index 7172147..495d620 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
@@ -38,6 +38,9 @@
             R.drawable.vector_icon_gradient_1,
             R.drawable.vector_icon_gradient_2,
             R.drawable.vector_icon_gradient_3,
+            R.drawable.vector_icon_gradient_1_clamp,
+            R.drawable.vector_icon_gradient_2_repeat,
+            R.drawable.vector_icon_gradient_3_mirror,
             R.drawable.vector_icon_state_list_simple,
             R.drawable.vector_icon_state_list_theme,
             R.drawable.vector_drawable01,