Merge "Update DocumentUI styles to match mocks."
diff --git a/api/current.txt b/api/current.txt
index e912440..eb888af 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4830,17 +4830,17 @@
     field public android.app.Notification.Action[] actions;
     field public android.media.AudioAttributes audioAttributes;
     field public deprecated int audioStreamType;
-    field public android.widget.RemoteViews bigContentView;
+    field public deprecated android.widget.RemoteViews bigContentView;
     field public java.lang.String category;
     field public int color;
     field public android.app.PendingIntent contentIntent;
-    field public android.widget.RemoteViews contentView;
+    field public deprecated android.widget.RemoteViews contentView;
     field public int defaults;
     field public android.app.PendingIntent deleteIntent;
     field public android.os.Bundle extras;
     field public int flags;
     field public android.app.PendingIntent fullScreenIntent;
-    field public android.widget.RemoteViews headsUpContentView;
+    field public deprecated android.widget.RemoteViews headsUpContentView;
     field public deprecated int icon;
     field public int iconLevel;
     field public deprecated android.graphics.Bitmap largeIcon;
@@ -4931,14 +4931,22 @@
     method public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
     method public deprecated android.app.Notification getNotification();
+    method public android.widget.RemoteViews makeBigContentView();
+    method public android.widget.RemoteViews makeContentView();
+    method public android.widget.RemoteViews makeHeadsUpContentView();
+    method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
+    method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
     method public android.app.Notification.Builder setAutoCancel(boolean);
     method public android.app.Notification.Builder setCategory(java.lang.String);
     method public android.app.Notification.Builder setColor(int);
-    method public android.app.Notification.Builder setContent(android.widget.RemoteViews);
+    method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
     method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setContentText(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence);
+    method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews);
     method public android.app.Notification.Builder setDefaults(int);
     method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setExtras(android.os.Bundle);
@@ -9409,6 +9417,7 @@
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
     field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
+    field public static final java.lang.String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management";
     field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
     field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
     field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
@@ -22153,6 +22162,7 @@
     field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259
     field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5
     field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305
+    field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff
     field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff
     field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb
     field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe
@@ -22176,6 +22186,7 @@
     field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef
     field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f
     field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43
+    field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2
     field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2
     field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400
     field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314
@@ -22265,6 +22276,7 @@
     field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51
     field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52
     field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50
+    field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20
     field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000
     field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6
     field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2
@@ -22310,6 +22322,7 @@
     field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064
     field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066
     field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a
+    field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1
     field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4
     field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5
     field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f
@@ -36464,6 +36477,7 @@
     method public boolean performHapticFeedback(int);
     method public boolean performHapticFeedback(int, int);
     method public boolean performLongClick();
+    method public boolean performLongClick(float, float);
     method public void playSoundEffect(int);
     method public boolean post(java.lang.Runnable);
     method public boolean postDelayed(java.lang.Runnable, long);
@@ -39199,6 +39213,7 @@
     method public abstract android.net.Uri getUrl();
     method public abstract boolean hasGesture();
     method public abstract boolean isForMainFrame();
+    method public abstract boolean isRedirect();
   }
 
   public class WebResourceResponse {
@@ -39561,7 +39576,8 @@
     method public deprecated android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String);
     method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest);
     method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent);
-    method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+    method public deprecated boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+    method public boolean shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest);
     field public static final int ERROR_AUTHENTICATION = -4; // 0xfffffffc
     field public static final int ERROR_BAD_URL = -12; // 0xfffffff4
     field public static final int ERROR_CONNECT = -6; // 0xfffffffa
diff --git a/api/system-current.txt b/api/system-current.txt
index b996af3..d288ad1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4947,17 +4947,17 @@
     field public android.app.Notification.Action[] actions;
     field public android.media.AudioAttributes audioAttributes;
     field public deprecated int audioStreamType;
-    field public android.widget.RemoteViews bigContentView;
+    field public deprecated android.widget.RemoteViews bigContentView;
     field public java.lang.String category;
     field public int color;
     field public android.app.PendingIntent contentIntent;
-    field public android.widget.RemoteViews contentView;
+    field public deprecated android.widget.RemoteViews contentView;
     field public int defaults;
     field public android.app.PendingIntent deleteIntent;
     field public android.os.Bundle extras;
     field public int flags;
     field public android.app.PendingIntent fullScreenIntent;
-    field public android.widget.RemoteViews headsUpContentView;
+    field public deprecated android.widget.RemoteViews headsUpContentView;
     field public deprecated int icon;
     field public int iconLevel;
     field public deprecated android.graphics.Bitmap largeIcon;
@@ -5048,14 +5048,22 @@
     method public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
     method public deprecated android.app.Notification getNotification();
+    method public android.widget.RemoteViews makeBigContentView();
+    method public android.widget.RemoteViews makeContentView();
+    method public android.widget.RemoteViews makeHeadsUpContentView();
+    method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
+    method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
     method public android.app.Notification.Builder setAutoCancel(boolean);
     method public android.app.Notification.Builder setCategory(java.lang.String);
     method public android.app.Notification.Builder setColor(int);
-    method public android.app.Notification.Builder setContent(android.widget.RemoteViews);
+    method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
     method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setContentText(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence);
+    method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews);
+    method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews);
     method public android.app.Notification.Builder setDefaults(int);
     method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setExtras(android.os.Bundle);
@@ -9703,6 +9711,7 @@
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
     field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
+    field public static final java.lang.String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management";
     field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
     field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
     field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
@@ -24098,6 +24107,7 @@
     field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259
     field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5
     field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305
+    field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff
     field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff
     field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb
     field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe
@@ -24121,6 +24131,7 @@
     field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef
     field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f
     field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43
+    field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2
     field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2
     field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400
     field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314
@@ -24210,6 +24221,7 @@
     field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51
     field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52
     field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50
+    field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20
     field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000
     field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6
     field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2
@@ -24255,6 +24267,7 @@
     field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064
     field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066
     field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a
+    field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1
     field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4
     field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5
     field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f
@@ -38761,6 +38774,7 @@
     method public boolean performHapticFeedback(int);
     method public boolean performHapticFeedback(int, int);
     method public boolean performLongClick();
+    method public boolean performLongClick(float, float);
     method public void playSoundEffect(int);
     method public boolean post(java.lang.Runnable);
     method public boolean postDelayed(java.lang.Runnable, long);
@@ -41559,6 +41573,7 @@
     method public abstract android.net.Uri getUrl();
     method public abstract boolean hasGesture();
     method public abstract boolean isForMainFrame();
+    method public abstract boolean isRedirect();
   }
 
   public class WebResourceResponse {
@@ -41968,7 +41983,8 @@
     method public deprecated android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String);
     method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest);
     method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent);
-    method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+    method public deprecated boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+    method public boolean shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest);
     field public static final int ERROR_AUTHENTICATION = -4; // 0xfffffffc
     field public static final int ERROR_BAD_URL = -12; // 0xfffffff4
     field public static final int ERROR_CONNECT = -6; // 0xfffffffa
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 3f8e311..2960cdc 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -283,7 +283,7 @@
             } else if (args.length == 2) {
                 if (args[0].equalsIgnoreCase("-p")) {
                     validCommand = true;
-                    return displayPackageFilePath(args[1], UserHandle.USER_OWNER);
+                    return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
                 }
             }
             return 1;
@@ -767,7 +767,7 @@
     }
 
     private int runPath() {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1650,7 +1650,7 @@
     }
 
     private int runClear() {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1722,7 +1722,7 @@
     }
 
     private int runSetEnabledSetting(int state) {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
@@ -1771,7 +1771,7 @@
     }
 
     private int runSetHiddenSetting(boolean state) {
-        int userId = UserHandle.USER_OWNER;
+        int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
         if (option != null && option.equals("--user")) {
             String optionData = nextOptionData();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6ae32d0..fce1b2e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1200,6 +1200,12 @@
         public int id;
 
         /**
+         * The stack that currently contains this task.
+         * @hide
+         */
+        public int stackId;
+
+        /**
          * The component launched as the first activity in the task.  This can
          * be considered the "application" of this task.
          */
@@ -1248,6 +1254,7 @@
 
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(id);
+            dest.writeInt(stackId);
             ComponentName.writeToParcel(baseActivity, dest);
             ComponentName.writeToParcel(topActivity, dest);
             if (thumbnail != null) {
@@ -1264,6 +1271,7 @@
 
         public void readFromParcel(Parcel source) {
             id = source.readInt();
+            stackId = source.readInt();
             baseActivity = ComponentName.readFromParcel(source);
             topActivity = ComponentName.readFromParcel(source);
             if (source.readInt() != 0) {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 933f98d..0266a37 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -23,10 +25,12 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IRemoteCallback;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.util.Pair;
 import android.util.Slog;
+import android.view.AppTransitionAnimationSpec;
 import android.view.View;
 import android.view.Window;
 
@@ -127,6 +131,17 @@
     public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
 
     /**
+     * Descriptions of app transition animations to be played during the activity launch.
+     */
+    private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
+
+    /**
+     * Where the docked stack should be positioned.
+     * @hide
+     */
+    private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
+
+    /**
      * For Activity transitions, the calling Activity's TransitionListener used to
      * notify the called Activity when the shared element and the exit transitions
      * complete.
@@ -190,6 +205,8 @@
     private int mResultCode;
     private int mExitCoordinatorIndex;
     private PendingIntent mUsageTimeReport;
+    private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+    private AppTransitionAnimationSpec mAnimSpecs[];
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -503,6 +520,18 @@
         return opts;
     }
 
+    /** @hide */
+    public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
+            AppTransitionAnimationSpec[] specs, Handler handler,
+            OnAnimationStartedListener listener) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = source.getContext().getPackageName();
+        opts.mAnimationType = ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+        opts.mAnimSpecs = specs;
+        opts.setOnAnimationStartedListener(handler, listener);
+        return opts;
+    }
+
     /**
      * Create an ActivityOptions to transition between Activities using cross-Activity scene
      * animations. This method carries the position of one shared element to the started Activity.
@@ -688,6 +717,14 @@
                 mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
                 break;
         }
+        mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+        if (opts.containsKey(KEY_ANIM_SPECS)) {
+            Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
+            mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
+            for (int i = specs.length - 1; i >= 0; i--) {
+                mAnimSpecs[i] = (AppTransitionAnimationSpec) specs[i];
+            }
+        }
     }
 
     /** @hide */
@@ -800,12 +837,23 @@
     }
 
     /** @hide */
+    public AppTransitionAnimationSpec[] getAnimSpecs() { return mAnimSpecs; }
+
+    /** @hide */
     public static void abort(Bundle options) {
         if (options != null) {
             (new ActivityOptions(options)).abort();
         }
     }
 
+    /** @hide */
+    public int getDockCreateMode() { return mDockCreateMode; }
+
+    /** @hide */
+    public void setDockCreateMode(int dockCreateMode) {
+        mDockCreateMode = dockCreateMode;
+    }
+
     /**
      * Update the current values in this ActivityOptions from those supplied
      * in <var>otherOptions</var>.  Any values
@@ -880,6 +928,7 @@
                 mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
                 break;
         }
+        mAnimSpecs = otherOptions.mAnimSpecs;
     }
 
     /**
@@ -945,6 +994,10 @@
                 b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
                 break;
         }
+        b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+        if (mAnimSpecs != null) {
+            b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
+        }
 
         return b;
     }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index db18722..49edff4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -251,15 +251,26 @@
     public RemoteViews tickerView;
 
     /**
-     * The view that will represent this notification in the expanded status bar.
+     * The view that will represent this notification in the notification list (which is pulled
+     * down from the status bar).
+     *
+     * As of N, this field is not used. The notification view is determined by the inputs to
+     * {@link Notification.Builder}; a custom RemoteViews can optionally be
+     * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
      */
+    @Deprecated
     public RemoteViews contentView;
 
     /**
      * A large-format version of {@link #contentView}, giving the Notification an
      * opportunity to show more detail. The system UI may choose to show this
      * instead of the normal content view at its discretion.
+     *
+     * As of N, this field is not used. The expanded notification view is determined by the
+     * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
+     * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
      */
+    @Deprecated
     public RemoteViews bigContentView;
 
 
@@ -268,7 +279,12 @@
      * opportunity to add action buttons to contentView. At its discretion, the system UI may
      * choose to show this as a heads-up notification, which will pop up so the user can see
      * it without leaving their current activity.
+     *
+     * As of N, this field is not used. The heads-up notification view is determined by the
+     * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
+     * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
      */
+    @Deprecated
     public RemoteViews headsUpContentView;
 
     /**
@@ -867,6 +883,11 @@
     public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId";
 
     /**
+     * @hide
+     */
+    public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
+
+    /**
      * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
      * displayed in the heads up space.
      *
@@ -1707,8 +1728,6 @@
             extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
             extras.remove(Notification.EXTRA_PICTURE);
             extras.remove(Notification.EXTRA_BIG_TEXT);
-            // Prevent light notifications from being rebuilt.
-            extras.remove(Builder.EXTRA_NEEDS_REBUILD);
         }
     }
 
@@ -1901,21 +1920,13 @@
     @Deprecated
     public void setLatestEventInfo(Context context,
             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
-        Notification.Builder builder = new Notification.Builder(context);
+        if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
+            Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
+                    new Throwable());
+        }
 
-        // First, ensure that key pieces of information that may have been set directly
-        // are preserved
-        builder.setWhen(this.when);
-        builder.setSmallIcon(this.icon);
-        builder.setPriority(this.priority);
-        builder.setTicker(this.tickerText);
-        builder.setNumber(this.number);
-        builder.setColor(this.color);
-        builder.mFlags = this.flags;
-        builder.setSound(this.sound, this.audioStreamType);
-        builder.setDefaults(this.defaults);
-        builder.setVibrate(this.vibrate);
-        builder.setDeleteIntent(this.deleteIntent);
+        // ensure that any information already set directly is preserved
+        final Notification.Builder builder = new Notification.Builder(context, this);
 
         // now apply the latestEventInfo fields
         if (contentTitle != null) {
@@ -1925,7 +1936,8 @@
             builder.setContentText(contentText);
         }
         builder.setContentIntent(contentIntent);
-        builder.buildInto(this);
+
+        builder.build(); // callers expect this notification to be ready to use
     }
 
     @Override
@@ -2080,15 +2092,6 @@
     /**
      * @hide
      */
-    public boolean isValid() {
-        // Would like to check for icon!=0 here, too, but NotificationManagerService accepts that
-        // for legacy reasons.
-        return contentView != null || extras.getBoolean(Builder.EXTRA_REBUILD_CONTENT_VIEW);
-    }
-
-    /**
-     * @hide
-     */
     public boolean isGroupSummary() {
         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
     }
@@ -2125,99 +2128,14 @@
         private static final int MAX_ACTION_BUTTONS = 3;
         private static final float LARGE_TEXT_SCALE = 1.3f;
 
-        /**
-         * @hide
-         */
-        public static final String EXTRA_NEEDS_REBUILD = "android.rebuild";
-
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_LARGE_ICON = "android.rebuild.largeIcon";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_CONTENT_VIEW = "android.rebuild.contentView";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
-                "android.rebuild.contentViewActionCount";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW
-                = "android.rebuild.bigView";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
-                = "android.rebuild.bigViewActionCount";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW
-                = "android.rebuild.hudView";
-        /**
-         * @hide
-         */
-        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
-                = "android.rebuild.hudViewActionCount";
-
-        /**
-         * The ApplicationInfo of the package that created the notification, used to create
-         * a context to rebuild the notification via a Builder.
-         * @hide
-         */
-        private static final String EXTRA_REBUILD_CONTEXT_APPLICATION_INFO =
-                "android.rebuild.applicationInfo";
-
-        // Whether to enable stripping (at post time) & rebuilding (at listener receive time) of
-        // memory intensive resources.
-        private static final boolean STRIP_AND_REBUILD = true;
-
         private Context mContext;
-
-        private long mWhen;
-        private Icon mSmallIcon, mLargeIcon;
-        private int mSmallIconLevel;
-        private int mNumber;
-        private CharSequence mContentTitle;
-        private CharSequence mContentText;
-        private CharSequence mContentInfo;
-        private CharSequence mSubText;
-        private PendingIntent mContentIntent;
-        private RemoteViews mContentView;
-        private PendingIntent mDeleteIntent;
-        private PendingIntent mFullScreenIntent;
-        private CharSequence mTickerText;
-        private RemoteViews mTickerView;
-        private Uri mSound;
-        private int mAudioStreamType;
-        private AudioAttributes mAudioAttributes;
-        private long[] mVibrate;
-        private int mLedArgb;
-        private int mLedOnMs;
-        private int mLedOffMs;
-        private int mDefaults;
-        private int mFlags;
-        private int mProgressMax;
-        private int mProgress;
-        private boolean mProgressIndeterminate;
-        private String mCategory;
-        private String mGroupKey;
-        private String mSortKey;
-        private Bundle mExtras;
-        private int mPriority;
-        private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
-        private boolean mUseChronometer;
+        private Notification mN;
+        private Bundle mUserExtras = new Bundle();
         private Style mStyle;
-        private boolean mShowWhen = true;
-        private int mVisibility = VISIBILITY_PRIVATE;
-        private Notification mPublicVersion = null;
-        private final NotificationColorUtil mColorUtil;
-        private ArrayList<String> mPeople;
-        private int mColor = COLOR_DEFAULT;
+        private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
+        private ArrayList<String> mPersonList = new ArrayList<String>();
+        private NotificationColorUtil mColorUtil;
+        private boolean mColorUtilInited = false;
         private List<Topic> mTopics = new ArrayList<>();
 
         /**
@@ -2226,25 +2144,6 @@
         private int mOriginatingUserId;
 
         /**
-         * Contains extras related to rebuilding during the build phase.
-         */
-        private Bundle mRebuildBundle = new Bundle();
-        /**
-         * Contains the notification to rebuild when this Builder is in "rebuild" mode.
-         * Null otherwise.
-         */
-        private Notification mRebuildNotification = null;
-
-        /**
-         * Whether the build notification has three lines. This is used to make the top padding for
-         * both the contracted and expanded layout consistent.
-         *
-         * <p>
-         * This field is only valid during the build phase.
-         */
-        private boolean mHasThreeLines;
-
-        /**
          * Constructs a new Builder with the defaults:
          *
 
@@ -2264,61 +2163,67 @@
          *            object.
          */
         public Builder(Context context) {
-            /*
-             * Important compatibility note!
-             * Some apps out in the wild create a Notification.Builder in their Activity subclass
-             * constructor for later use. At this point Activities - themselves subclasses of
-             * ContextWrapper - do not have their inner Context populated yet. This means that
-             * any calls to Context methods from within this constructor can cause NPEs in existing
-             * apps. Any data populated from mContext should therefore be populated lazily to
-             * preserve compatibility.
-             */
-            mContext = context;
-
-            // Set defaults to match the defaults of a Notification
-            mWhen = System.currentTimeMillis();
-            mAudioStreamType = STREAM_DEFAULT;
-            mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
-            mPriority = PRIORITY_DEFAULT;
-            mPeople = new ArrayList<String>();
-
-            mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP ?
-                    NotificationColorUtil.getInstance(mContext) : null;
+            this(context, null);
         }
 
         /**
-         * Creates a Builder for rebuilding the given Notification.
-         * <p>
-         * Call {@link #rebuild()} to retrieve the rebuilt version of 'n'.
+         * @hide
          */
-        private Builder(Context context, Notification n) {
-            this(context);
-            mRebuildNotification = n;
-            restoreFromNotification(n);
+        public Builder(Context context, Notification toAdopt) {
+            mContext = context;
 
-            Style style = null;
-            Bundle extras = n.extras;
-            String templateClass = extras.getString(EXTRA_TEMPLATE);
-            if (!TextUtils.isEmpty(templateClass)) {
-                Class<? extends Style> styleClass = getNotificationStyleClass(templateClass);
-                if (styleClass == null) {
-                    Log.d(TAG, "Unknown style class: " + styleClass);
-                    return;
+            if (toAdopt == null) {
+                mN = new Notification();
+                mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
+                mN.priority = PRIORITY_DEFAULT;
+                mN.visibility = VISIBILITY_PRIVATE;
+            } else {
+                mN = toAdopt;
+                if (mN.actions != null) {
+                    Collections.addAll(mActions, mN.actions);
                 }
 
-                try {
-                    Constructor<? extends Style> constructor = styleClass.getConstructor();
-                    constructor.setAccessible(true);
-                    style = constructor.newInstance();
-                    style.restoreFromExtras(extras);
-                } catch (Throwable t) {
-                    Log.e(TAG, "Could not create Style", t);
-                    return;
+                if (mN.extras.containsKey(EXTRA_PEOPLE)) {
+                    Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE));
+                }
+
+                if (mN.getTopics() != null) {
+                    Collections.addAll(mTopics, mN.getTopics());
+                }
+
+                String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
+                if (!TextUtils.isEmpty(templateClass)) {
+                    final Class<? extends Style> styleClass
+                            = getNotificationStyleClass(templateClass);
+                    if (styleClass == null) {
+                        Log.d(TAG, "Unknown style class: " + templateClass);
+                    } else {
+                        try {
+                            final Constructor<? extends Style> ctor = styleClass.getConstructor();
+                            ctor.setAccessible(true);
+                            final Style style = ctor.newInstance();
+                            style.restoreFromExtras(mN.extras);
+
+                            if (style != null) {
+                                setStyle(style);
+                            }
+                        } catch (Throwable t) {
+                            Log.e(TAG, "Could not create Style", t);
+                        }
+                    }
+                }
+
+            }
+        }
+
+        private NotificationColorUtil getColorUtil() {
+            if (!mColorUtilInited) {
+                mColorUtilInited = true;
+                if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
+                    mColorUtil = NotificationColorUtil.getInstance(mContext);
                 }
             }
-            if (style != null) {
-                setStyle(style);
-            }
+            return mColorUtil;
         }
 
         /**
@@ -2329,7 +2234,7 @@
          * @see Notification#when
          */
         public Builder setWhen(long when) {
-            mWhen = when;
+            mN.when = when;
             return this;
         }
 
@@ -2338,7 +2243,7 @@
          * in the content view.
          */
         public Builder setShowWhen(boolean show) {
-            mShowWhen = show;
+            mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
             return this;
         }
 
@@ -2354,7 +2259,7 @@
          * @see Notification#when
          */
         public Builder setUsesChronometer(boolean b) {
-            mUseChronometer = b;
+            mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
             return this;
         }
 
@@ -2390,7 +2295,7 @@
          * @see Notification#iconLevel
          */
         public Builder setSmallIcon(@DrawableRes int icon, int level) {
-            mSmallIconLevel = level;
+            mN.iconLevel = level;
             return setSmallIcon(icon);
         }
 
@@ -2403,7 +2308,10 @@
          * @see Notification#icon
          */
         public Builder setSmallIcon(Icon icon) {
-            mSmallIcon = icon;
+            mN.setSmallIcon(icon);
+            if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
+                mN.icon = icon.getResId();
+            }
             return this;
         }
 
@@ -2411,7 +2319,7 @@
          * Set the first line of text in the platform notification template.
          */
         public Builder setContentTitle(CharSequence title) {
-            mContentTitle = safeCharSequence(title);
+            mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
             return this;
         }
 
@@ -2419,7 +2327,7 @@
          * Set the second line of text in the platform notification template.
          */
         public Builder setContentText(CharSequence text) {
-            mContentText = safeCharSequence(text);
+            mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
             return this;
         }
 
@@ -2429,7 +2337,7 @@
          * same location in the standard template.
          */
         public Builder setSubText(CharSequence text) {
-            mSubText = safeCharSequence(text);
+            mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
             return this;
         }
 
@@ -2439,7 +2347,7 @@
          * font size for readability.
          */
         public Builder setNumber(int number) {
-            mNumber = number;
+            mN.number = number;
             return this;
         }
 
@@ -2450,7 +2358,7 @@
          * right (to the right of a smallIcon if it has been placed there).
          */
         public Builder setContentInfo(CharSequence info) {
-            mContentInfo = safeCharSequence(info);
+            mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
             return this;
         }
 
@@ -2460,19 +2368,52 @@
          * The platform template will represent this using a {@link ProgressBar}.
          */
         public Builder setProgress(int max, int progress, boolean indeterminate) {
-            mProgressMax = max;
-            mProgress = progress;
-            mProgressIndeterminate = indeterminate;
+            mN.extras.putInt(EXTRA_PROGRESS, progress);
+            mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
+            mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
             return this;
         }
 
         /**
          * Supply a custom RemoteViews to use instead of the platform template.
          *
-         * @see Notification#contentView
+         * Use {@link #setCustomContentView(RemoteViews)} instead.
          */
+        @Deprecated
         public Builder setContent(RemoteViews views) {
-            mContentView = views;
+            return setCustomContentView(views);
+        }
+
+        /**
+         * Supply custom RemoteViews to use instead of the platform template.
+         *
+         * This will override the layout that would otherwise be constructed by this Builder
+         * object.
+         */
+        public Builder setCustomContentView(RemoteViews contentView) {
+            mN.contentView = contentView;
+            return this;
+        }
+
+        /**
+         * Supply custom RemoteViews to use instead of the platform template in the expanded form.
+         *
+         * This will override the expanded layout that would otherwise be constructed by this
+         * Builder object.
+         */
+        public Builder setCustomBigContentView(RemoteViews contentView) {
+            mN.bigContentView = contentView;
+            return this;
+        }
+
+        /**
+         * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
+         *
+         * This will override the heads-up layout that would otherwise be constructed by this
+         * Builder object.
+         */
+        public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
+            mN.headsUpContentView = contentView;
             return this;
         }
 
@@ -2488,7 +2429,7 @@
          * @see Notification#contentIntent Notification.contentIntent
          */
         public Builder setContentIntent(PendingIntent intent) {
-            mContentIntent = intent;
+            mN.contentIntent = intent;
             return this;
         }
 
@@ -2498,7 +2439,7 @@
          * @see Notification#deleteIntent
          */
         public Builder setDeleteIntent(PendingIntent intent) {
-            mDeleteIntent = intent;
+            mN.deleteIntent = intent;
             return this;
         }
 
@@ -2523,7 +2464,7 @@
          * @see Notification#fullScreenIntent
          */
         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
-            mFullScreenIntent = intent;
+            mN.fullScreenIntent = intent;
             setFlag(FLAG_HIGH_PRIORITY, highPriority);
             return this;
         }
@@ -2534,7 +2475,7 @@
          * @see Notification#tickerText
          */
         public Builder setTicker(CharSequence tickerText) {
-            mTickerText = safeCharSequence(tickerText);
+            mN.tickerText = safeCharSequence(tickerText);
             return this;
         }
 
@@ -2544,8 +2485,8 @@
          */
         @Deprecated
         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
-            mTickerText = safeCharSequence(tickerText);
-            mTickerView = views; // we'll save it for you anyway
+            setTicker(tickerText);
+            // views is ignored
             return this;
         }
 
@@ -2568,7 +2509,8 @@
          * badge atop the large icon).
          */
         public Builder setLargeIcon(Icon icon) {
-            mLargeIcon = icon;
+            mN.mLargeIcon = icon;
+            mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
             return this;
         }
 
@@ -2585,8 +2527,8 @@
          * @see Notification#sound
          */
         public Builder setSound(Uri sound) {
-            mSound = sound;
-            mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
+            mN.sound = sound;
+            mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
             return this;
         }
 
@@ -2603,8 +2545,8 @@
          */
         @Deprecated
         public Builder setSound(Uri sound, int streamType) {
-            mSound = sound;
-            mAudioStreamType = streamType;
+            mN.sound = sound;
+            mN.audioStreamType = streamType;
             return this;
         }
 
@@ -2619,8 +2561,8 @@
          * @see Notification#sound
          */
         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
-            mSound = sound;
-            mAudioAttributes = audioAttributes;
+            mN.sound = sound;
+            mN.audioAttributes = audioAttributes;
             return this;
         }
 
@@ -2637,7 +2579,7 @@
          * @see Notification#vibrate
          */
         public Builder setVibrate(long[] pattern) {
-            mVibrate = pattern;
+            mN.vibrate = pattern;
             return this;
         }
 
@@ -2654,9 +2596,9 @@
          * @see Notification#ledOffMS
          */
         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
-            mLedArgb = argb;
-            mLedOnMs = onMs;
-            mLedOffMs = offMs;
+            mN.ledARGB = argb;
+            mN.ledOnMS = onMs;
+            mN.ledOffMS = offMs;
             return this;
         }
 
@@ -2724,7 +2666,7 @@
          * For all default values, use {@link #DEFAULT_ALL}.
          */
         public Builder setDefaults(int defaults) {
-            mDefaults = defaults;
+            mN.defaults = defaults;
             return this;
         }
 
@@ -2734,7 +2676,7 @@
          * @see Notification#priority
          */
         public Builder setPriority(@Priority int pri) {
-            mPriority = pri;
+            mN.priority = pri;
             return this;
         }
 
@@ -2744,7 +2686,7 @@
          * @see Notification#category
          */
         public Builder setCategory(String category) {
-            mCategory = category;
+            mN.category = category;
             return this;
         }
 
@@ -2771,7 +2713,7 @@
          * @see Notification#EXTRA_PEOPLE
          */
         public Builder addPerson(String uri) {
-            mPeople.add(uri);
+            mPersonList.add(uri);
             return this;
         }
 
@@ -2787,7 +2729,7 @@
          * @return this object for method chaining
          */
         public Builder setGroup(String groupKey) {
-            mGroupKey = groupKey;
+            mN.mGroupKey = groupKey;
             return this;
         }
 
@@ -2816,7 +2758,7 @@
          * @see String#compareTo(String)
          */
         public Builder setSortKey(String sortKey) {
-            mSortKey = sortKey;
+            mN.mSortKey = sortKey;
             return this;
         }
 
@@ -2829,11 +2771,7 @@
          */
         public Builder addExtras(Bundle extras) {
             if (extras != null) {
-                if (mExtras == null) {
-                    mExtras = new Bundle(extras);
-                } else {
-                    mExtras.putAll(extras);
-                }
+                mUserExtras.putAll(extras);
             }
             return this;
         }
@@ -2851,7 +2789,9 @@
          * @see Notification#extras
          */
         public Builder setExtras(Bundle extras) {
-            mExtras = extras;
+            if (extras != null) {
+                mUserExtras = extras;
+            }
             return this;
         }
 
@@ -2866,10 +2806,13 @@
          * @see Notification#extras
          */
         public Bundle getExtras() {
-            if (mExtras == null) {
-                mExtras = new Bundle();
-            }
-            return mExtras;
+            return mUserExtras;
+        }
+
+        private Bundle getAllExtras() {
+            final Bundle saveExtras = (Bundle) mUserExtras.clone();
+            saveExtras.putAll(mN.extras);
+            return saveExtras;
         }
 
         /**
@@ -2918,6 +2861,21 @@
         }
 
         /**
+         * Alter the complete list of actions attached to this notification.
+         * @see #addAction(Action).
+         *
+         * @param actions
+         * @return
+         */
+        public Builder setActions(Action... actions) {
+            mActions.clear();
+            for (int i = 0; i < actions.length; i++) {
+                mActions.add(actions[i]);
+            }
+            return this;
+        }
+
+        /**
          * Add a rich notification style to be applied at build time.
          *
          * @param style Object responsible for modifying the notification style.
@@ -2927,6 +2885,9 @@
                 mStyle = style;
                 if (mStyle != null) {
                     mStyle.setBuilder(this);
+                    mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
+                }  else {
+                    mN.extras.remove(EXTRA_TEMPLATE);
                 }
             }
             return this;
@@ -2941,7 +2902,7 @@
          * @return The same Builder.
          */
         public Builder setVisibility(int visibility) {
-            mVisibility = visibility;
+            mN.visibility = visibility;
             return this;
         }
 
@@ -2952,7 +2913,12 @@
          * @return The same Builder.
          */
         public Builder setPublicVersion(Notification n) {
-            mPublicVersion = n;
+            if (n != null) {
+                mN.publicVersion = new Notification();
+                n.cloneInto(mN.publicVersion, /*heavy=*/ true);
+            } else {
+                mN.publicVersion = null;
+            }
             return this;
         }
 
@@ -2970,9 +2936,9 @@
          */
         public void setFlag(int mask, boolean value) {
             if (value) {
-                mFlags |= mask;
+                mN.flags |= mask;
             } else {
-                mFlags &= ~mask;
+                mN.flags &= ~mask;
             }
         }
 
@@ -2984,7 +2950,7 @@
          * @return The same Builder.
          */
         public Builder setColor(@ColorInt int argb) {
-            mColor = argb;
+            mN.color = argb;
             return this;
         }
 
@@ -3075,7 +3041,6 @@
             contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
             contentView.setViewVisibility(R.id.progress, View.GONE);
             contentView.setViewVisibility(R.id.chronometer, View.GONE);
-            contentView.setViewVisibility(R.id.time, View.GONE);
         }
 
         private RemoteViews applyStandardTemplate(int resId) {
@@ -3093,39 +3058,43 @@
             boolean showLine3 = false;
             boolean showLine2 = false;
             boolean contentTextInLine2 = false;
+            final Bundle ex = mN.extras;
 
-            if (mLargeIcon != null) {
-                contentView.setImageViewIcon(R.id.icon, mLargeIcon);
-                processLargeLegacyIcon(mLargeIcon, contentView);
-                contentView.setImageViewIcon(R.id.right_icon, mSmallIcon);
+            if (mN.mLargeIcon != null) {
+                contentView.setImageViewIcon(R.id.icon, mN.mLargeIcon);
+                processLargeLegacyIcon(mN.mLargeIcon, contentView);
+                contentView.setImageViewIcon(R.id.right_icon, mN.mSmallIcon);
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
-                processSmallRightIcon(mSmallIcon, contentView);
+                processSmallRightIcon(mN.mSmallIcon, contentView);
             } else { // small icon at left
-                contentView.setImageViewIcon(R.id.icon, mSmallIcon);
+                contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
                 contentView.setViewVisibility(R.id.icon, View.VISIBLE);
-                processSmallIconAsLarge(mSmallIcon, contentView);
+                processSmallIconAsLarge(mN.mSmallIcon, contentView);
             }
-            if (mContentTitle != null) {
-                contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle));
+            if (ex.getCharSequence(EXTRA_TITLE) != null) {
+                contentView.setTextViewText(R.id.title,
+                        processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
             }
-            if (mContentText != null) {
-                contentView.setTextViewText(R.id.text, processLegacyText(mContentText));
+            if (ex.getCharSequence(EXTRA_TEXT) != null) {
+                contentView.setTextViewText(R.id.text,
+                        processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
                 showLine3 = true;
             }
-            if (mContentInfo != null) {
-                contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo));
+            if (ex.getCharSequence(EXTRA_INFO_TEXT) != null) {
+                contentView.setTextViewText(R.id.info,
+                        processLegacyText(ex.getCharSequence(EXTRA_INFO_TEXT)));
                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
                 showLine3 = true;
-            } else if (mNumber > 0) {
+            } else if (mN.number > 0) {
                 final int tooBig = mContext.getResources().getInteger(
                         R.integer.status_bar_notification_info_maxnum);
-                if (mNumber > tooBig) {
+                if (mN.number > tooBig) {
                     contentView.setTextViewText(R.id.info, processLegacyText(
                             mContext.getResources().getString(
                                     R.string.status_bar_notification_info_overflow)));
                 } else {
                     NumberFormat f = NumberFormat.getIntegerInstance();
-                    contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber)));
+                    contentView.setTextViewText(R.id.info, processLegacyText(f.format(mN.number)));
                 }
                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
                 showLine3 = true;
@@ -3134,10 +3103,12 @@
             }
 
             // Need to show three lines?
-            if (mSubText != null) {
-                contentView.setTextViewText(R.id.text, processLegacyText(mSubText));
-                if (mContentText != null) {
-                    contentView.setTextViewText(R.id.text2, processLegacyText(mContentText));
+            if (ex.getCharSequence(EXTRA_SUB_TEXT) != null) {
+                contentView.setTextViewText(R.id.text,
+                        processLegacyText(ex.getCharSequence(EXTRA_SUB_TEXT)));
+                if (ex.getCharSequence(EXTRA_TEXT) != null) {
+                    contentView.setTextViewText(R.id.text2,
+                            processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
                     contentView.setViewVisibility(R.id.text2, View.VISIBLE);
                     showLine2 = true;
                     contentTextInLine2 = true;
@@ -3146,15 +3117,18 @@
                 }
             } else {
                 contentView.setViewVisibility(R.id.text2, View.GONE);
-                if (hasProgress && (mProgressMax != 0 || mProgressIndeterminate)) {
+                final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+                final int progress = ex.getInt(EXTRA_PROGRESS, 0);
+                final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+                if (hasProgress && (max != 0 || ind)) {
                     contentView.setViewVisibility(R.id.progress, View.VISIBLE);
                     contentView.setProgressBar(
-                            R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
+                            R.id.progress, max, progress, ind);
                     contentView.setProgressBackgroundTintList(
                             R.id.progress, ColorStateList.valueOf(mContext.getColor(
                                     R.color.notification_progress_background_color)));
-                    if (mColor != COLOR_DEFAULT) {
-                        ColorStateList colorStateList = ColorStateList.valueOf(mColor);
+                    if (mN.color != COLOR_DEFAULT) {
+                        ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
                         contentView.setProgressTintList(R.id.progress, colorStateList);
                         contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
                     }
@@ -3170,20 +3144,21 @@
             }
 
             if (showsTimeOrChronometer()) {
-                if (mUseChronometer) {
+                if (ex.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
                     contentView.setLong(R.id.chronometer, "setBase",
-                            mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+                            mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
                 } else {
                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
-                    contentView.setLong(R.id.time, "setTime", mWhen);
+                    contentView.setLong(R.id.time, "setTime", mN.when);
                 }
             }
 
             // Adjust padding depending on line count and font size.
-            contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext,
-                    mHasThreeLines, mContext.getResources().getConfiguration().fontScale),
+            contentView.setViewPadding(R.id.line1, 0,
+                    calculateTopPadding(mContext, hasThreeLines(),
+                            mContext.getResources().getConfiguration().fontScale),
                     0, 0);
 
             // We want to add badge to first line of text.
@@ -3196,7 +3171,8 @@
 
             // Note getStandardView may hide line 3 again.
             contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
-            contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE);
+            contentView.setViewVisibility(R.id.overflow_divider,
+                    showLine3 ? View.VISIBLE : View.GONE);
             return contentView;
         }
 
@@ -3205,7 +3181,7 @@
          *         otherwise
          */
         private boolean showsTimeOrChronometer() {
-            return mWhen != 0 && mShowWhen;
+            return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN);
         }
 
         /**
@@ -3216,15 +3192,19 @@
          *         is going to have one or two lines
          */
         private boolean hasThreeLines() {
-            boolean contentTextInLine2 = mSubText != null && mContentText != null;
+            final CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+            final CharSequence text = mN.extras.getCharSequence(EXTRA_TEXT);
+            boolean contentTextInLine2 = subText != null && text != null;
             boolean hasProgress = mStyle == null || mStyle.hasProgress();
             // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
             boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
-            boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0
-                    || badgeInLine3;
-            boolean hasLine2 = (mSubText != null && mContentText != null) ||
-                    (hasProgress && mSubText == null
-                            && (mProgressMax != 0 || mProgressIndeterminate));
+            boolean hasLine3 = text != null || mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null
+                    || mN.number > 0 || badgeInLine3;
+            final Bundle ex = mN.extras;
+            final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+            final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+            boolean hasLine2 = (subText != null && text != null) ||
+                    (hasProgress && subText == null && (max != 0 || ind));
             return hasLine2 && hasLine3;
         }
 
@@ -3271,29 +3251,48 @@
             return big;
         }
 
-        private RemoteViews makeContentView() {
-            if (mContentView != null) {
-                return mContentView;
-            } else {
-                return applyStandardTemplate(getBaseLayoutResource());
+        /**
+         * Construct a RemoteViews for the final 1U notification layout. In order:
+         *   1. Custom contentView from the caller
+         *   2. Style's proposed content view
+         *   3. Standard template view
+         */
+        public RemoteViews makeContentView() {
+            if (mN.contentView != null) {
+                return mN.contentView;
+            } else if (mStyle != null) {
+                final RemoteViews styleView = mStyle.makeContentView();
+                if (styleView != null) {
+                    return styleView;
+                }
             }
+            return applyStandardTemplate(getBaseLayoutResource());
         }
 
-        private RemoteViews makeTickerView() {
-            if (mTickerView != null) {
-                return mTickerView;
-            }
-            return null; // tickers are not created by default anymore
-        }
-
-        private RemoteViews makeBigContentView() {
-            if (mActions.size() == 0) return null;
+        /**
+         * Construct a RemoteViews for the final big notification layout.
+         */
+        public RemoteViews makeBigContentView() {
+            if (mStyle != null) {
+                final RemoteViews styleView = mStyle.makeBigContentView();
+                if (styleView != null) {
+                    return styleView;
+                }
+            } else if (mActions.size() == 0) return null;
 
             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
         }
 
-        private RemoteViews makeHeadsUpContentView() {
-            if (mActions.size() == 0) return null;
+        /**
+         * Construct a RemoteViews for the final heads-up notification layout.
+         */
+        public RemoteViews makeHeadsUpContentView() {
+            if (mStyle != null) {
+                final RemoteViews styleView = mStyle.makeHeadsUpContentView();
+                if (styleView != null) {
+                    return styleView;
+                }
+            } else if (mActions.size() == 0) return null;
 
             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
         }
@@ -3320,11 +3319,11 @@
          *         doesn't create material notifications by itself) app.
          */
         private boolean isLegacy() {
-            return mColorUtil != null;
+            return getColorUtil() != null;
         }
 
         private void processLegacyAction(Action action, RemoteViews button) {
-            if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.getIcon())) {
+            if (!isLegacy() || getColorUtil().isGrayscaleIcon(mContext, action.getIcon())) {
                 button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0,
                         mContext.getColor(R.color.notification_action_color_filter),
                         PorterDuff.Mode.MULTIPLY);
@@ -3333,7 +3332,7 @@
 
         private CharSequence processLegacyText(CharSequence charSequence) {
             if (isLegacy()) {
-                return mColorUtil.invertCharSequenceColors(charSequence);
+                return getColorUtil().invertCharSequenceColors(charSequence);
             } else {
                 return charSequence;
             }
@@ -3349,7 +3348,7 @@
                         PorterDuff.Mode.SRC_ATOP, -1);
                 applyLargeIconBackground(contentView);
             } else {
-                if (mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
+                if (getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                     applyLargeIconBackground(contentView);
                 }
             }
@@ -3362,7 +3361,7 @@
         // TODO: also check bounds, transparency, that sort of thing.
         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
             if (largeIcon != null && isLegacy()
-                    && mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
+                    && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                 applyLargeIconBackground(contentView);
             } else {
                 removeLargeIconBackground(contentView);
@@ -3404,7 +3403,7 @@
             }
             final boolean gray = isLegacy()
                     && smallIcon.getType() == Icon.TYPE_RESOURCE
-                    && mColorUtil.isGrayscaleIcon(mContext, smallIcon.getResId());
+                    && getColorUtil().isGrayscaleIcon(mContext, smallIcon.getResId());
             if (!isLegacy() || gray) {
                 contentView.setInt(R.id.right_icon,
                         "setBackgroundResource",
@@ -3421,17 +3420,17 @@
         }
 
         private int sanitizeColor() {
-            if (mColor != COLOR_DEFAULT) {
-                mColor |= 0xFF000000; // no alpha for custom colors
+            if (mN.color != COLOR_DEFAULT) {
+                mN.color |= 0xFF000000; // no alpha for custom colors
             }
-            return mColor;
+            return mN.color;
         }
 
         private int resolveColor() {
-            if (mColor == COLOR_DEFAULT) {
+            if (mN.color == COLOR_DEFAULT) {
                 return mContext.getColor(R.color.notification_icon_bg_color);
             }
-            return mColor;
+            return mN.color;
         }
 
         /**
@@ -3439,165 +3438,25 @@
          * @hide
          */
         public Notification buildUnstyled() {
-            Notification n = new Notification();
-            n.when = mWhen;
-            n.mSmallIcon = mSmallIcon;
-            if (mSmallIcon != null && mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
-                n.icon = mSmallIcon.getResId();
-            }
-            n.iconLevel = mSmallIconLevel;
-            n.number = mNumber;
-
-            n.color = sanitizeColor();
-
-            setBuilderContentView(n, makeContentView());
-            n.contentIntent = mContentIntent;
-            n.deleteIntent = mDeleteIntent;
-            n.fullScreenIntent = mFullScreenIntent;
-            n.tickerText = mTickerText;
-            n.tickerView = makeTickerView();
-            n.mLargeIcon = mLargeIcon;
-            if (mLargeIcon != null && mLargeIcon.getType() == Icon.TYPE_BITMAP) {
-                n.largeIcon = mLargeIcon.getBitmap();
-            }
-            n.sound = mSound;
-            n.audioStreamType = mAudioStreamType;
-            n.audioAttributes = mAudioAttributes;
-            n.vibrate = mVibrate;
-            n.ledARGB = mLedArgb;
-            n.ledOnMS = mLedOnMs;
-            n.ledOffMS = mLedOffMs;
-            n.defaults = mDefaults;
-            n.flags = mFlags;
-            setBuilderBigContentView(n, makeBigContentView());
-            setBuilderHeadsUpContentView(n, makeHeadsUpContentView());
-            if (mLedOnMs != 0 || mLedOffMs != 0) {
-                n.flags |= FLAG_SHOW_LIGHTS;
-            }
-            if ((mDefaults & DEFAULT_LIGHTS) != 0) {
-                n.flags |= FLAG_SHOW_LIGHTS;
-            }
-            n.category = mCategory;
-            n.mGroupKey = mGroupKey;
-            n.mSortKey = mSortKey;
-            n.priority = mPriority;
             if (mActions.size() > 0) {
-                n.actions = new Action[mActions.size()];
-                mActions.toArray(n.actions);
+                mN.actions = new Action[mActions.size()];
+                mActions.toArray(mN.actions);
             }
-            n.visibility = mVisibility;
-
-            if (mPublicVersion != null) {
-                n.publicVersion = new Notification();
-                mPublicVersion.cloneInto(n.publicVersion, true);
+            if (!mPersonList.isEmpty()) {
+                mN.extras.putStringArray(EXTRA_PEOPLE,
+                        mPersonList.toArray(new String[mPersonList.size()]));
             }
             if (mTopics.size() > 0) {
-                n.topics = new Topic[mTopics.size()];
-                mTopics.toArray(n.topics);
+                mN.topics = new Topic[mTopics.size()];
+                mTopics.toArray(mN.topics);
             }
-            // Note: If you're adding new fields, also update restoreFromNotitification().
-            return n;
+            return mN;
         }
 
-        /**
-         * Capture, in the provided bundle, semantic information used in the construction of
-         * this Notification object.
-         * @hide
-         */
-        public void populateExtras(Bundle extras) {
-            // Store original information used in the construction of this object
-            extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
-            extras.putParcelable(EXTRA_REBUILD_CONTEXT_APPLICATION_INFO,
-                    mContext.getApplicationInfo());
-            extras.putCharSequence(EXTRA_TITLE, mContentTitle);
-            extras.putCharSequence(EXTRA_TEXT, mContentText);
-            extras.putCharSequence(EXTRA_SUB_TEXT, mSubText);
-            extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo);
-            extras.putParcelable(EXTRA_SMALL_ICON, mSmallIcon);
-            extras.putInt(EXTRA_PROGRESS, mProgress);
-            extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax);
-            extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
-            extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
-            extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
-            if (mLargeIcon != null) {
-                extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
-            }
-            if (!mPeople.isEmpty()) {
-                extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
-            }
-            // NOTE: If you're adding new extras also update restoreFromNotification().
-        }
-
-
-        /**
-         * @hide
-         */
-        public static void stripForDelivery(Notification n) {
-            if (!STRIP_AND_REBUILD) {
-                return;
-            }
-
-            String templateClass = n.extras.getString(EXTRA_TEMPLATE);
-            // Only strip views for known Styles because we won't know how to
-            // re-create them otherwise.
-            boolean stripViews = TextUtils.isEmpty(templateClass) ||
-                    getNotificationStyleClass(templateClass) != null;
-
-            boolean isStripped = false;
-
-            if (n.largeIcon != null && n.extras.containsKey(EXTRA_LARGE_ICON)) {
-                // TODO: Would like to check for equality here, but if the notification
-                // has been cloned, we can't.
-                n.largeIcon = null;
-                n.extras.putBoolean(EXTRA_REBUILD_LARGE_ICON, true);
-                isStripped = true;
-            }
-            // Get rid of unmodified BuilderRemoteViews.
-
-            if (stripViews &&
-                    n.contentView instanceof BuilderRemoteViews &&
-                    n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.contentView.getSequenceNumber()) {
-                n.contentView = null;
-                n.extras.putBoolean(EXTRA_REBUILD_CONTENT_VIEW, true);
-                n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
-                isStripped = true;
-            }
-            if (stripViews &&
-                    n.bigContentView instanceof BuilderRemoteViews &&
-                    n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.bigContentView.getSequenceNumber()) {
-                n.bigContentView = null;
-                n.extras.putBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW, true);
-                n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
-                isStripped = true;
-            }
-            if (stripViews &&
-                    n.headsUpContentView instanceof BuilderRemoteViews &&
-                    n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
-                            n.headsUpContentView.getSequenceNumber()) {
-                n.headsUpContentView = null;
-                n.extras.putBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW, true);
-                n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
-                isStripped = true;
-            }
-
-            if (isStripped) {
-                n.extras.putBoolean(EXTRA_NEEDS_REBUILD, true);
-            }
-        }
-
-        /**
-         * @hide
-         */
-        public static Notification rebuild(Context context, Notification n) {
-            Bundle extras = n.extras;
-            if (!extras.getBoolean(EXTRA_NEEDS_REBUILD)) return n;
-            extras.remove(EXTRA_NEEDS_REBUILD);
-
+        public static Notification.Builder recoverBuilder(Context context, Notification n) {
             // Re-create notification context so we can access app resources.
-            ApplicationInfo applicationInfo = extras.getParcelable(
-                    EXTRA_REBUILD_CONTEXT_APPLICATION_INFO);
+            ApplicationInfo applicationInfo = n.extras.getParcelable(
+                    EXTRA_BUILDER_APPLICATION_INFO);
             Context builderContext;
             try {
                 builderContext = context.createApplicationContext(applicationInfo,
@@ -3607,58 +3466,7 @@
                 builderContext = context;  // try with our context
             }
 
-            Builder b = new Builder(builderContext, n);
-            return b.rebuild();
-        }
-
-        /**
-         * Rebuilds the notification passed in to the rebuild-constructor
-         * {@link #Builder(Context, Notification)}.
-         *
-         * <p>
-         * Throws IllegalStateException when invoked on a Builder that isn't in rebuild mode.
-         *
-         * @hide
-         */
-        private Notification rebuild() {
-            if (mRebuildNotification == null) {
-                throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode.");
-            }
-            mHasThreeLines = hasThreeLines();
-
-            Bundle extras = mRebuildNotification.extras;
-
-            if (extras.getBoolean(EXTRA_REBUILD_LARGE_ICON)) {
-                mRebuildNotification.largeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
-            }
-            extras.remove(EXTRA_REBUILD_LARGE_ICON);
-
-            if (extras.getBoolean(EXTRA_REBUILD_CONTENT_VIEW)) {
-                setBuilderContentView(mRebuildNotification, makeContentView());
-                if (mStyle != null) {
-                    mStyle.populateContentView(mRebuildNotification);
-                }
-            }
-            extras.remove(EXTRA_REBUILD_CONTENT_VIEW);
-
-            if (extras.getBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW)) {
-                setBuilderBigContentView(mRebuildNotification, makeBigContentView());
-                if (mStyle != null) {
-                    mStyle.populateBigContentView(mRebuildNotification);
-                }
-            }
-            extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW);
-
-            if (extras.getBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW)) {
-                setBuilderHeadsUpContentView(mRebuildNotification, makeHeadsUpContentView());
-                if (mStyle != null) {
-                    mStyle.populateHeadsUpContentView(mRebuildNotification);
-                }
-            }
-            extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW);
-
-            mHasThreeLines = false;
-            return mRebuildNotification;
+            return new Builder(builderContext, n);
         }
 
         private static Class<? extends Style> getNotificationStyleClass(String templateClass) {
@@ -3674,91 +3482,15 @@
 
         private void setBuilderContentView(Notification n, RemoteViews contentView) {
             n.contentView = contentView;
-            if (contentView instanceof BuilderRemoteViews) {
-                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
-                        contentView.getSequenceNumber());
-            }
         }
 
         private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) {
             n.bigContentView = bigContentView;
-            if (bigContentView instanceof BuilderRemoteViews) {
-                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
-                        bigContentView.getSequenceNumber());
-            }
         }
 
         private void setBuilderHeadsUpContentView(Notification n,
                 RemoteViews headsUpContentView) {
             n.headsUpContentView = headsUpContentView;
-            if (headsUpContentView instanceof BuilderRemoteViews) {
-                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
-                        headsUpContentView.getSequenceNumber());
-            }
-        }
-
-        private void restoreFromNotification(Notification n) {
-
-            // Notification fields.
-            mWhen = n.when;
-            mSmallIcon = n.mSmallIcon;
-            mSmallIconLevel = n.iconLevel;
-            mNumber = n.number;
-
-            mColor = n.color;
-
-            mContentView = n.contentView;
-            mDeleteIntent = n.deleteIntent;
-            mFullScreenIntent = n.fullScreenIntent;
-            mTickerText = n.tickerText;
-            mTickerView = n.tickerView;
-            mLargeIcon = n.mLargeIcon;
-            mSound = n.sound;
-            mAudioStreamType = n.audioStreamType;
-            mAudioAttributes = n.audioAttributes;
-
-            mVibrate = n.vibrate;
-            mLedArgb = n.ledARGB;
-            mLedOnMs = n.ledOnMS;
-            mLedOffMs = n.ledOffMS;
-            mDefaults = n.defaults;
-            mFlags = n.flags;
-
-            mCategory = n.category;
-            mGroupKey = n.mGroupKey;
-            mSortKey = n.mSortKey;
-            mPriority = n.priority;
-            mActions.clear();
-            if (n.actions != null) {
-                Collections.addAll(mActions, n.actions);
-            }
-            mVisibility = n.visibility;
-
-            mPublicVersion = n.publicVersion;
-
-            if (n.topics != null) {
-                Collections.addAll(mTopics, n.topics);
-            }
-
-            // Extras.
-            Bundle extras = n.extras;
-            mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID);
-            mContentTitle = extras.getCharSequence(EXTRA_TITLE);
-            mContentText = extras.getCharSequence(EXTRA_TEXT);
-            mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
-            mContentInfo = extras.getCharSequence(EXTRA_INFO_TEXT);
-            mProgress = extras.getInt(EXTRA_PROGRESS);
-            mProgressMax = extras.getInt(EXTRA_PROGRESS_MAX);
-            mProgressIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
-            mUseChronometer = extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
-            mShowWhen = extras.getBoolean(EXTRA_SHOW_WHEN);
-            if (extras.containsKey(EXTRA_LARGE_ICON)) {
-                mLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
-            }
-            if (extras.containsKey(EXTRA_PEOPLE)) {
-                mPeople.clear();
-                Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE));
-            }
         }
 
         /**
@@ -3774,38 +3506,23 @@
          * object.
          */
         public Notification build() {
-            if (mSmallIcon != null) {
-                mSmallIcon.convertToAshmem();
+            // first, add any extras from the calling code
+            if (mUserExtras != null) {
+                mN.extras = getAllExtras();
             }
-            if (mLargeIcon != null) {
-                mLargeIcon.convertToAshmem();
-            }
+
+            // lazy stuff from mContext; see comment in Builder(Context, Notification)
+            mN.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, mContext.getApplicationInfo());
             mOriginatingUserId = mContext.getUserId();
-            mHasThreeLines = hasThreeLines();
+            mN.extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
 
-            Notification n = buildUnstyled();
+            buildUnstyled();
 
             if (mStyle != null) {
-                mStyle.purgeResources();
-                n = mStyle.buildStyled(n);
+                mStyle.buildStyled(mN);
             }
 
-            if (mExtras != null) {
-                n.extras.putAll(mExtras);
-            }
-
-            if (mRebuildBundle.size() > 0) {
-                n.extras.putAll(mRebuildBundle);
-                mRebuildBundle.clear();
-            }
-
-            populateExtras(n.extras);
-            if (mStyle != null) {
-                mStyle.addExtras(n.extras);
-            }
-
-            mHasThreeLines = false;
-            return n;
+            return mN;
         }
 
         /**
@@ -3901,14 +3618,15 @@
             checkBuilder();
 
             // Nasty.
-            CharSequence oldBuilderContentTitle = mBuilder.mContentTitle;
+            CharSequence oldBuilderContentTitle =
+                    mBuilder.getAllExtras().getCharSequence(EXTRA_TITLE);
             if (mBigContentTitle != null) {
                 mBuilder.setContentTitle(mBigContentTitle);
             }
 
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
 
-            mBuilder.mContentTitle = oldBuilderContentTitle;
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TITLE, oldBuilderContentTitle);
 
             if (mBigContentTitle != null && mBigContentTitle.equals("")) {
                 contentView.setViewVisibility(R.id.line1, View.GONE);
@@ -3919,7 +3637,7 @@
             // The last line defaults to the subtext, but can be replaced by mSummaryText
             final CharSequence overflowText =
                     mSummaryTextSet ? mSummaryText
-                                    : mBuilder.mSubText;
+                                    : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT);
             if (overflowText != null) {
                 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
                 contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
@@ -3935,6 +3653,31 @@
         }
 
         /**
+         * Construct a Style-specific RemoteViews for the final 1U notification layout.
+         * The default implementation has nothing additional to add.
+         * @hide
+         */
+        public RemoteViews makeContentView() {
+            return null;
+        }
+
+        /**
+         * Construct a Style-specific RemoteViews for the final big notification layout.
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
+            return null;
+        }
+
+        /**
+         * Construct a Style-specific RemoteViews for the final HUN layout.
+         * @hide
+         */
+        public RemoteViews makeHeadsUpContentView() {
+            return null;
+        }
+
+        /**
          * Changes the padding of the first line such that the big and small content view have the
          * same top padding.
          *
@@ -3942,12 +3685,13 @@
          */
         protected void applyTopPadding(RemoteViews contentView) {
             int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
-                    mBuilder.mHasThreeLines,
+                    mBuilder.hasThreeLines(),
                     mBuilder.mContext.getResources().getConfiguration().fontScale);
             contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
         }
 
         /**
+         * Apply any style-specific extras to this notification before shipping it out.
          * @hide
          */
         public void addExtras(Bundle extras) {
@@ -3961,6 +3705,7 @@
         }
 
         /**
+         * Reconstruct the internal state of this Style object from extras.
          * @hide
          */
         protected void restoreFromExtras(Bundle extras) {
@@ -3978,10 +3723,7 @@
          * @hide
          */
         public Notification buildStyled(Notification wip) {
-            populateTickerView(wip);
-            populateContentView(wip);
-            populateBigContentView(wip);
-            populateHeadsUpContentView(wip);
+            addExtras(wip.extras);
             return wip;
         }
 
@@ -3990,26 +3732,6 @@
          */
         public void purgeResources() {}
 
-        // The following methods are split out so we can re-create notification partially.
-        /**
-         * @hide
-         */
-        protected void populateTickerView(Notification wip) {}
-        /**
-         * @hide
-         */
-        protected void populateContentView(Notification wip) {}
-
-        /**
-         * @hide
-         */
-        protected void populateBigContentView(Notification wip) {}
-
-        /**
-         * @hide
-         */
-        protected void populateHeadsUpContentView(Notification wip) {}
-
         /**
          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
          * attached to.
@@ -4115,29 +3837,33 @@
             }
         }
 
-        private RemoteViews makeBigContentView() {
-            // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
+            // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
             // This covers the following cases:
             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
-            //          mLargeIcon
-            //   2. !mBigLargeIconSet -> mLargeIcon applies
+            //          mN.mLargeIcon
+            //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
             Icon oldLargeIcon = null;
             if (mBigLargeIconSet) {
-                oldLargeIcon = mBuilder.mLargeIcon;
-                mBuilder.mLargeIcon = mBigLargeIcon;
+                oldLargeIcon = mBuilder.mN.mLargeIcon;
+                mBuilder.mN.mLargeIcon = mBigLargeIcon;
             }
 
             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
 
             if (mBigLargeIconSet) {
-                mBuilder.mLargeIcon = oldLargeIcon;
+                mBuilder.mN.mLargeIcon = oldLargeIcon;
             }
 
             contentView.setImageViewBitmap(R.id.big_picture, mPicture);
 
             applyTopPadding(contentView);
 
-            boolean twoTextLines = mBuilder.mSubText != null && mBuilder.mContentText != null;
+            boolean twoTextLines = mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT) != null
+                    && mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT) != null;
             mBuilder.addProfileBadge(contentView,
                     twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
             return contentView;
@@ -4168,14 +3894,6 @@
             }
             mPicture = extras.getParcelable(EXTRA_PICTURE);
         }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
-        }
     }
 
     /**
@@ -4255,15 +3973,19 @@
             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
         }
 
-        private RemoteViews makeBigContentView() {
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
 
             // Nasty
-            CharSequence oldBuilderContentText = mBuilder.mContentText;
-            mBuilder.mContentText = null;
+            CharSequence oldBuilderContentText =
+                    mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT);
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
 
             RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
 
-            mBuilder.mContentText = oldBuilderContentText;
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
 
             contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
             contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
@@ -4282,7 +4004,8 @@
         private int calculateMaxLines() {
             int lineCount = MAX_LINES;
             boolean hasActions = mBuilder.mActions.size() > 0;
-            boolean hasSummary = (mSummaryTextSet ? mSummaryText : mBuilder.mSubText) != null;
+            boolean hasSummary = (mSummaryTextSet ? mSummaryText
+                    : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT)) != null;
             if (hasActions) {
                 lineCount -= LINES_CONSUMED_BY_ACTIONS;
             }
@@ -4291,19 +4014,11 @@
             }
 
             // If we have less top padding at the top, we can fit less lines.
-            if (!mBuilder.mHasThreeLines) {
+            if (!mBuilder.hasThreeLines()) {
                 lineCount--;
             }
             return lineCount;
         }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
-        }
     }
 
     /**
@@ -4384,16 +4099,18 @@
             }
         }
 
-        private RemoteViews makeBigContentView() {
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
             // Remove the content text so line3 disappears unless you have a summary
-
             // Nasty
-            CharSequence oldBuilderContentText = mBuilder.mContentText;
-            mBuilder.mContentText = null;
+            CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT);
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
 
             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
 
-            mBuilder.mContentText = oldBuilderContentText;
+            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
 
             contentView.setViewVisibility(R.id.text2, View.GONE);
 
@@ -4437,14 +4154,6 @@
 
             return contentView;
         }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
-        }
     }
 
     /**
@@ -4536,16 +4245,16 @@
          * @hide
          */
         @Override
-        public void populateContentView(Notification wip) {
-            mBuilder.setBuilderContentView(wip, makeMediaContentView());
+        public RemoteViews makeContentView() {
+            return makeMediaContentView();
         }
 
         /**
          * @hide
          */
         @Override
-        public void populateBigContentView(Notification wip) {
-            mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView());
+        public RemoteViews makeBigContentView() {
+            return makeMediaBigContentView();
         }
 
         /** @hide */
@@ -4659,7 +4368,7 @@
                     R.color.notification_media_secondary_color);
             contentView.setTextColor(R.id.title, primaryColor);
             if (mBuilder.showsTimeOrChronometer()) {
-                if (mBuilder.mUseChronometer) {
+                if (mBuilder.getAllExtras().getBoolean(EXTRA_SHOW_CHRONOMETER)) {
                     contentView.setTextColor(R.id.chronometer, secondaryColor);
                 } else {
                     contentView.setTextColor(R.id.time, secondaryColor);
@@ -5503,7 +5212,7 @@
         /**
          * Gets the accent color.
          *
-         * @see setColor
+         * @see #setColor
          */
         @ColorInt
         public int getColor() {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index cb0ff33..f75b22a 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -207,33 +207,7 @@
      */
     public void notify(String tag, int id, Notification notification)
     {
-        int[] idOut = new int[1];
-        INotificationManager service = getService();
-        String pkg = mContext.getPackageName();
-        if (notification.sound != null) {
-            notification.sound = notification.sound.getCanonicalUri();
-            if (StrictMode.vmFileUriExposureEnabled()) {
-                notification.sound.checkFileUriExposed("Notification.sound");
-            }
-        }
-        fixLegacySmallIcon(notification, pkg);
-        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
-            if (notification.getSmallIcon() == null) {
-                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
-                    + notification);
-            }
-        }
-        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
-        Notification stripped = notification.clone();
-        Builder.stripForDelivery(stripped);
-        try {
-            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
-                    stripped, idOut, UserHandle.myUserId());
-            if (id != idOut[0]) {
-                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
-            }
-        } catch (RemoteException e) {
-        }
+        notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
     }
 
     /**
@@ -251,12 +225,17 @@
             }
         }
         fixLegacySmallIcon(notification, pkg);
+        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+            if (notification.getSmallIcon() == null) {
+                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+                        + notification);
+            }
+        }
         if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
-        Notification stripped = notification.clone();
-        Builder.stripForDelivery(stripped);
+        final Notification copy = notification.clone();
         try {
             service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
-                    stripped, idOut, user.getIdentifier());
+                    copy, idOut, user.getIdentifier());
             if (id != idOut[0]) {
                 Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
             }
@@ -287,13 +266,7 @@
      */
     public void cancel(String tag, int id)
     {
-        INotificationManager service = getService();
-        String pkg = mContext.getPackageName();
-        if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
-        try {
-            service.cancelNotificationWithTag(pkg, tag, id, UserHandle.myUserId());
-        } catch (RemoteException e) {
-        }
+        cancelAsUser(tag, id, new UserHandle(UserHandle.myUserId()));
     }
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 0a0d77d..4270e16 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,8 @@
 
 package android.app.admin;
 
+import android.os.Bundle;
+
 import java.util.List;
 
 /**
@@ -69,4 +71,13 @@
      * @return true if the uid is an active admin with the given policy.
      */
     public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
+
+    /**
+     * Takes a {@link Bundle} containing "base" user restrictions stored in
+     * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner
+     * and the profile owner and returns the merged restrictions.
+     *
+     * This method always returns a new {@link Bundle}.
+     */
+    public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
 }
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index ff4ebee..874026f 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -1059,6 +1059,41 @@
     }
 
     /**
+     * Sets whether audio routing is allowed.
+     *
+     * Note: This is an internal function and shouldn't be exposed
+     */
+    public void setAudioRouteAllowed(boolean allowed) {
+        if (VDBG) log("setAudioRouteAllowed");
+        if (mService != null && isEnabled()) {
+            try {
+                mService.setAudioRouteAllowed(allowed);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Returns whether audio routing is allowed.
+     *
+     * Note: This is an internal function and shouldn't be exposed
+     */
+    public boolean getAudioRouteAllowed() {
+        if (VDBG) log("getAudioRouteAllowed");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getAudioRouteAllowed();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
      * Initiates a connection of audio channel.
      *
      * It setup SCO channel with remote connected Handsfree AG device.
diff --git a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
index e518b7d..79ae4e4 100644
--- a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
@@ -62,6 +62,8 @@
     int getAudioState(in BluetoothDevice device);
     boolean connectAudio();
     boolean disconnectAudio();
+    void setAudioRouteAllowed(boolean allowed);
+    boolean getAudioRouteAllowed();
 
     Bundle getCurrentAgFeatures(in BluetoothDevice device);
 }
diff --git a/core/java/android/content/pm/AppsQueryHelper.java b/core/java/android/content/pm/AppsQueryHelper.java
index 56b3173..a5a8e3f 100644
--- a/core/java/android/content/pm/AppsQueryHelper.java
+++ b/core/java/android/content/pm/AppsQueryHelper.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -46,6 +48,14 @@
      */
     public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1;
 
+    /**
+     * Return all input methods that are marked as default.
+     * <p>When this flag is set, {@code user} specified in
+     * {@link #queryApps(int, boolean, UserHandle)} must be
+     * {@link UserHandle#myUserId user of the current process}.
+     */
+    public static int GET_DEFAULT_IMES = 1 << 2;
+
     private final Context mContext;
     private List<ApplicationInfo> mAllApps;
 
@@ -56,13 +66,14 @@
     /**
      * Return a List of all packages that satisfy a specified criteria.
      * @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS},
-     * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM}
+     * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_DEFAULT_IMES}.
      * @param systemAppsOnly if true, only system apps will be returned
      * @param user user, whose apps are queried
      */
     public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) {
         boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0;
         boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0;
+        boolean defaultImes = (flags & GET_DEFAULT_IMES) > 0;
         if (mAllApps == null) {
             mAllApps = getAllApps(user.getIdentifier());
         }
@@ -118,6 +129,33 @@
             }
         }
 
+        if (defaultImes) {
+            if (UserHandle.myUserId() != user.getIdentifier()) {
+                throw new IllegalArgumentException("Specified user handle " + user
+                        + " is not a user of the current process.");
+            }
+            List<InputMethodInfo> imis = getInputMethodList();
+            int imisSize = imis.size();
+            ArraySet<String> defaultImePackages = new ArraySet<>();
+            for (int i = 0; i < imisSize; i++) {
+                InputMethodInfo imi = imis.get(i);
+                if (imi.isDefault(mContext)) {
+                    defaultImePackages.add(imi.getPackageName());
+                }
+            }
+            final int allAppsSize = mAllApps.size();
+            for (int i = 0; i < allAppsSize; i++) {
+                final ApplicationInfo appInfo = mAllApps.get(i);
+                if (systemAppsOnly && !appInfo.isSystemApp()) {
+                    continue;
+                }
+                final String packageName = appInfo.packageName;
+                if (defaultImePackages.contains(packageName)) {
+                    result.add(packageName);
+                }
+            }
+        }
+
         return result;
     }
 
@@ -150,4 +188,12 @@
             throw new IllegalStateException("Package manager has died", e);
         }
     }
+
+    @VisibleForTesting
+    @SuppressWarnings("unchecked")
+    protected List<InputMethodInfo> getInputMethodList() {
+        InputMethodManager imm = (InputMethodManager)
+                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+        return imm.getInputMethodList();
+    }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aed1a0b..27ecf9f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1708,6 +1708,15 @@
     public static final String FEATURE_BACKUP = "android.software.backup";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports freeform window management.
+     * Windows have title bars and can be moved and resized.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT
+            = "android.software.freeform_window_management";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device supports creating secondary users and managed profiles via
      * {@link DevicePolicyManager}.
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 1fcfaca..51796eb 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -513,7 +513,7 @@
      * {@link CameraManager#registerAvailabilityCallback} to be notified of such availability
      * changes.</p>
      *
-     * @see registerAvailabilityCallback
+     * @see #registerAvailabilityCallback
      */
     public static abstract class AvailabilityCallback {
 
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 6bfa2a4..1bb0fbb 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -119,8 +119,12 @@
             //
             // For one such example of this, see b/18867306.
             //
-            // TODO: Remove this special case altogether.
-            if (before.isIPv4Provisioned() && !after.isIPv4Provisioned()) {
+            // Additionally, losing IPv6 provisioning can result in TCP
+            // connections getting stuck until timeouts fire and other
+            // baffling failures. Therefore, loss of either IPv4 or IPv6 on a
+            // previously dualstack network is deemed a lost of provisioning.
+            if ((before.isIPv4Provisioned() && !after.isIPv4Provisioned()) ||
+                (before.isIPv6Provisioned() && !after.isIPv6Provisioned())) {
                 return ProvisioningChange.LOST_PROVISIONING;
             }
             return ProvisioningChange.STILL_PROVISIONED;
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3ade170..12cac853 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -55,14 +55,12 @@
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
     boolean hasUserRestriction(in String restrictionKey, int userHandle);
-    void setUserRestrictions(in Bundle restrictions, int userHandle);
     void setUserRestriction(String key, boolean value, int userId);
     void setSystemControlledUserRestriction(String key, boolean value, int userId);
     void setApplicationRestrictions(in String packageName, in Bundle restrictions,
             int userHandle);
     Bundle getApplicationRestrictions(in String packageName);
     Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle);
-    void removeRestrictions();
     void setDefaultGuestRestrictions(in Bundle restrictions);
     Bundle getDefaultGuestRestrictions();
     boolean markGuestForDeletion(int userHandle);
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index d64273a..73c2c80 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -44,7 +44,7 @@
     private FastPrintWriter mOutPrintWriter;
     private FastPrintWriter mErrPrintWriter;
 
-    public void exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ResultReceiver resultReceiver) {
         mTarget = target;
         mIn = in;
@@ -89,6 +89,7 @@
             mResultReceiver.send(res, null);
         }
         if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
+        return res;
     }
 
     public PrintWriter getOutPrintWriter() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1c1575e..17e1a28 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
@@ -55,7 +56,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
@@ -67,7 +69,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
@@ -78,7 +81,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_INSTALL_APPS = "no_install_apps";
@@ -89,7 +93,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -102,7 +107,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
@@ -114,7 +120,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";
@@ -127,7 +134,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
@@ -139,7 +147,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
@@ -150,7 +159,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
@@ -163,7 +173,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_REMOVE_USER = "no_remove_user";
@@ -174,7 +185,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
@@ -187,7 +199,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -199,7 +212,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
@@ -213,7 +227,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
@@ -227,7 +242,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_FACTORY_RESET = "no_factory_reset";
@@ -241,7 +257,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_ADD_USER = "no_add_user";
@@ -252,7 +269,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
@@ -266,7 +284,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
@@ -280,7 +299,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
@@ -300,7 +320,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
@@ -312,7 +333,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
@@ -324,7 +346,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
@@ -336,7 +359,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
@@ -350,7 +374,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
@@ -361,7 +386,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_SMS = "no_sms";
@@ -373,7 +399,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_FUN = "no_fun";
@@ -393,7 +420,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
@@ -406,7 +434,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
@@ -417,7 +446,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
@@ -426,7 +456,8 @@
      * Hidden user restriction to disallow access to wallpaper manager APIs. This user restriction
      * is always set for managed profiles.
      * @hide
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_WALLPAPER = "no_wallpaper";
@@ -438,7 +469,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_SAFE_BOOT = "no_safe_boot";
@@ -447,7 +479,8 @@
      * Specifies if a user is not allowed to record audio. This restriction is always enabled for
      * background users. The default value is <code>false</code>.
      *
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      * @hide
      */
@@ -466,7 +499,8 @@
      *
      * <p/>Key for user restrictions.
      * <p/>Type: Boolean
-     * @see #setUserRestrictions(Bundle)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
     public static final String ALLOW_PARENT_PROFILE_APP_LINKING
@@ -740,36 +774,23 @@
     }
 
     /**
-     * Sets all the user-wide restrictions for this user.
-     * Requires the MANAGE_USERS permission.
-     * @param restrictions the Bundle containing all the restrictions.
-     * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
-     * android.content.ComponentName, String)} or
-     * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
-     * android.content.ComponentName, String)} instead.
+     * This will no longer work.  Device owners and profile owners should use
+     * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead.
      */
+    // System apps should use UserManager.setUserRestriction() instead.
     @Deprecated
     public void setUserRestrictions(Bundle restrictions) {
-        setUserRestrictions(restrictions, Process.myUserHandle());
+        throw new UnsupportedOperationException("This method is no longer supported");
     }
 
     /**
-     * Sets all the user-wide restrictions for the specified user.
-     * Requires the MANAGE_USERS permission.
-     * @param restrictions the Bundle containing all the restrictions.
-     * @param userHandle the UserHandle of the user for whom to set the restrictions.
-     * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
-     * android.content.ComponentName, String)} or
-     * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
-     * android.content.ComponentName, String)} instead.
+     * This will no longer work.  Device owners and profile owners should use
+     * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead.
      */
+    // System apps should use UserManager.setUserRestriction() instead.
     @Deprecated
     public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
-        try {
-            mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not set user restrictions", re);
-        }
+        throw new UnsupportedOperationException("This method is no longer supported");
     }
 
     /**
@@ -784,9 +805,7 @@
      */
     @Deprecated
     public void setUserRestriction(String key, boolean value) {
-        Bundle bundle = getUserRestrictions();
-        bundle.putBoolean(key, value);
-        setUserRestrictions(bundle);
+        setUserRestriction(key, value, Process.myUserHandle());
     }
 
     /**
@@ -882,9 +901,8 @@
         try {
             user = mService.createUser(name, flags);
             if (user != null && !user.isAdmin()) {
-                Bundle userRestrictions = mService.getUserRestrictions(user.id);
-                addDefaultUserRestrictions(userRestrictions);
-                mService.setUserRestrictions(userRestrictions, user.id);
+                mService.setUserRestriction(DISALLOW_SMS, true, user.id);
+                mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id);
             }
         } catch (RemoteException re) {
             Log.w(TAG, "Could not create a user", re);
@@ -899,27 +917,22 @@
      * @hide
      */
     public UserInfo createGuest(Context context, String name) {
-        UserInfo guest = createUser(name, UserInfo.FLAG_GUEST);
-        if (guest != null) {
-            Settings.Secure.putStringForUser(context.getContentResolver(),
-                    Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
-            try {
-                Bundle guestRestrictions = mService.getDefaultGuestRestrictions();
-                guestRestrictions.putBoolean(DISALLOW_SMS, true);
-                guestRestrictions.putBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
-                mService.setUserRestrictions(guestRestrictions, guest.id);
-            } catch (RemoteException re) {
-                Log.w(TAG, "Could not update guest restrictions");
+        UserInfo guest = null;
+        try {
+            guest = mService.createUser(name, UserInfo.FLAG_GUEST);
+            if (guest != null) {
+                Settings.Secure.putStringForUser(context.getContentResolver(),
+                        Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+
+                mService.setUserRestriction(DISALLOW_SMS, true, guest.id);
+                mService.setUserRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES, true, guest.id);
             }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not create a user", re);
         }
         return guest;
     }
 
-    private static void addDefaultUserRestrictions(Bundle restrictions) {
-        restrictions.putBoolean(DISALLOW_OUTGOING_CALLS, true);
-        restrictions.putBoolean(DISALLOW_SMS, true);
-    }
-
     /**
      * Creates a user with the specified name and options as a profile of another user.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
@@ -1465,15 +1478,6 @@
         return false;
     }
 
-    /** @hide */
-    public void removeRestrictions() {
-        try {
-            mService.removeRestrictions();
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not change restrictions pin");
-        }
-    }
-
     /**
      * @hide
      * Set restrictions that should apply to any future guest user that's created.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
new file mode 100644
index 0000000..d7be6d8
--- /dev/null
+++ b/core/java/android/os/UserManagerInternal.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class UserManagerInternal {
+    /**
+     * Lock that must be held when calling certain methods in this class.
+     *
+     * This is used to avoid dead lock between
+     * {@link com.android.server.pm.UserManagerService} and
+     * {@link com.android.server.devicepolicy.DevicePolicyManagerService}.  This lock should not
+     * be newly taken while holding the DPMS lock, which would cause a dead lock.  Take this
+     * lock first before taking the DPMS lock to avoid that.
+     */
+    public abstract Object getUserRestrictionsLock();
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
+     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
+     *
+     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
+     */
+    public abstract void updateEffectiveUserRestrictionsRL(int userId);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
+     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
+     *
+     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
+     */
+    public abstract void updateEffectiveUserRestrictionsForAllUsersRL();
+
+    /**
+     * Returns the "base" user restrictions.
+     *
+     * Used by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+     * from MNC.
+     */
+    public abstract Bundle getBaseUserRestrictions(int userId);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+     * from MNC.
+     */
+    public abstract void setBaseUserRestrictionsByDpmsForMigration(int userId,
+            Bundle baseRestrictions);
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 11effd0..a1f9743 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6277,6 +6277,15 @@
         */
        public static final String FORCE_ALLOW_ON_EXTERNAL = "force_allow_on_external";
 
+        /**
+         * Whether any activity can be resized. When this is true, any
+         * activity, regardless of manifest values, can be resized for multi-window.
+         * (0 = false, 1 = true)
+         * @hide
+         */
+        public static final String DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES
+                = "force_resizable_activities";
+
        /**
         * Whether user has enabled development settings.
         */
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index d424546..7e7b5fc 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -31,6 +31,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.graphics.Bitmap;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -472,9 +473,10 @@
                 StatusBarNotification sbn = list.get(i);
                 Notification notification = sbn.getNotification();
                 try {
-                    Builder.rebuild(getContext(), notification);
                     // convert icon metadata to legacy format for older clients
                     createLegacyIconExtras(notification);
+                    // populate remote views for older clients.
+                    maybePopulateRemoteViews(notification);
                 } catch (IllegalArgumentException e) {
                     if (corruptNotifications == null) {
                         corruptNotifications = new ArrayList<>(N);
@@ -676,6 +678,18 @@
         }
     }
 
+    /**
+     * Populates remote views for pre-N targeting apps.
+     */
+    private void maybePopulateRemoteViews(Notification notification) {
+        if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+            Builder builder = Builder.recoverBuilder(getContext(), notification);
+            notification.contentView = builder.makeContentView();
+            notification.bigContentView = builder.makeBigContentView();
+            notification.headsUpContentView = builder.makeHeadsUpContentView();
+        }
+    }
+
     private class INotificationListenerWrapper extends INotificationListener.Stub {
         @Override
         public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
@@ -689,9 +703,10 @@
             }
 
             try {
-                Notification.Builder.rebuild(getContext(), sbn.getNotification());
+                Notification notification = sbn.getNotification();
                 // convert icon metadata to legacy format for older clients
                 createLegacyIconExtras(sbn.getNotification());
+                maybePopulateRemoteViews(sbn.getNotification());
             } catch (IllegalArgumentException e) {
                 // drop corrupt notification
                 sbn = null;
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index de509b2..2459cfa 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -238,6 +238,7 @@
             initialScrollY = Touch.getInitialScrollY(widget, buffer);
         }
 
+        boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer);
         boolean handled = Touch.onTouchEvent(widget, buffer, event);
 
         if (widget.didTouchFocusSelect() && !isMouse) {
@@ -267,9 +268,9 @@
                 // Cursor can be active at any location in the text while mouse pointer can start
                 // selection from a totally different location. Use LAST_TAP_DOWN span to ensure
                 // text selection will start from mouse pointer location.
+                final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
                 if (isMouse && Touch.isSelectionStarted(buffer)) {
-                    int offset = buffer.getSpanStart(LAST_TAP_DOWN);
-                    Selection.setSelection(buffer, offset);
+                    Selection.setSelection(buffer, startOffset);
                 }
 
                 if (isTouchSelecting(isMouse, buffer) && handled) {
@@ -284,9 +285,9 @@
                     // Update selection as we're moving the selection area.
 
                     // Get the current touch position
-                    int offset = widget.getOffsetForPosition(event.getX(), event.getY());
-
-                    Selection.extendSelection(buffer, offset);
+                    final int offset = widget.getOffsetForPosition(event.getX(), event.getY());
+                    Selection.setSelection(buffer, Math.min(startOffset, offset),
+                            Math.max(startOffset, offset));
                     return true;
                 }
             } else if (action == MotionEvent.ACTION_UP) {
@@ -300,10 +301,12 @@
                     return true;
                 }
 
-                int offset = widget.getOffsetForPosition(event.getX(), event.getY());
-                if (isTouchSelecting(isMouse, buffer)) {
+                if (wasTouchSelecting) {
+                    final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
+                    final int endOffset = widget.getOffsetForPosition(event.getX(), event.getY());
+                    Selection.setSelection(buffer, Math.min(startOffset, endOffset),
+                            Math.max(startOffset, endOffset));
                     buffer.removeSpan(LAST_TAP_DOWN);
-                    Selection.extendSelection(buffer, offset);
                 }
 
                 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index c1eb80d..37d6757 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -343,15 +343,15 @@
         int seconds = (int) Math.floor(duration / 1000);
         int days = 0, hours = 0, minutes = 0;
 
-        if (seconds > SECONDS_PER_DAY) {
+        if (seconds >= SECONDS_PER_DAY) {
             days = seconds / SECONDS_PER_DAY;
             seconds -= days * SECONDS_PER_DAY;
         }
-        if (seconds > SECONDS_PER_HOUR) {
+        if (seconds >= SECONDS_PER_HOUR) {
             hours = seconds / SECONDS_PER_HOUR;
             seconds -= hours * SECONDS_PER_HOUR;
         }
-        if (seconds > SECONDS_PER_MINUTE) {
+        if (seconds >= SECONDS_PER_MINUTE) {
             minutes = seconds / SECONDS_PER_MINUTE;
             seconds -= minutes * SECONDS_PER_MINUTE;
         }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index fa86c74..8b804e8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2415,6 +2415,7 @@
      *                     1             PFLAG3_SCROLL_INDICATOR_START
      *                    1              PFLAG3_SCROLL_INDICATOR_END
      *                   1               PFLAG3_ASSIST_BLOCKED
+     *            1111111                PFLAG3_POINTER_ICON_MASK
      * |-------|-------|-------|-------|
      */
 
@@ -2611,6 +2612,36 @@
     static final int PFLAG3_ASSIST_BLOCKED = 0x4000;
 
     /**
+     * The mask for use with private flags indicating bits used for pointer icon shapes.
+     */
+    static final int PFLAG3_POINTER_ICON_MASK = 0x7f8000;
+
+    /**
+     * Left-shift used for pointer icon shape values in private flags.
+     */
+    static final int PFLAG3_POINTER_ICON_LSHIFT = 15;
+
+    /**
+     * Value indicating {@link PointerIcon.STYLE_NOT_SPECIFIED}.
+     */
+    private static final int PFLAG3_POINTER_ICON_NOT_SPECIFIED = 0 << PFLAG3_POINTER_ICON_LSHIFT;
+
+    /**
+     * Value indicating {@link PointerIcon.STYLE_NULL}.
+     */
+    private static final int PFLAG3_POINTER_ICON_NULL = 1 << PFLAG3_POINTER_ICON_LSHIFT;
+
+    /**
+     * Value incicating {@link PointerIcon.STYLE_CUSTOM}.
+     */
+    private static final int PFLAG3_POINTER_ICON_CUSTOM = 2 << PFLAG3_POINTER_ICON_LSHIFT;
+
+    /**
+     * The base value for other pointer icon shapes.
+     */
+    private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT;
+
+    /**
      * Always allow a user to over-scroll this view, provided it is a
      * view that can scroll.
      *
@@ -5341,21 +5372,53 @@
     }
 
     /**
-     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
-     * OnLongClickListener did not consume the event.
+     * Calls this view's OnLongClickListener, if it is defined. Invokes the
+     * context menu if the OnLongClickListener did not consume the event.
      *
-     * @return True if one of the above receivers consumed the event, false otherwise.
+     * @return {@code true} if one of the above receivers consumed the event,
+     *         {@code false} otherwise
      */
     public boolean performLongClick() {
+        return performLongClickInternal(false, 0, 0);
+    }
+
+    /**
+     * Calls this view's OnLongClickListener, if it is defined. Invokes the
+     * context menu if the OnLongClickListener did not consume the event,
+     * anchoring it to an (x,y) coordinate.
+     *
+     * @param x x coordinate of the anchoring touch event
+     * @param y y coordinate of the anchoring touch event
+     * @return {@code true} if one of the above receivers consumed the event,
+     *         {@code false} otherwise
+     */
+    public boolean performLongClick(float x, float y) {
+        return performLongClickInternal(true, x, y);
+    }
+
+    /**
+     * Calls this view's OnLongClickListener, if it is defined. Invokes the
+     * context menu if the OnLongClickListener did not consume the event,
+     * optionally anchoring it to an (x,y) coordinate.
+     *
+     * @param isAnchored whether this long click is anchored to a touch event
+     * @param x x coordinate of the anchoring touch event, ignored if
+     *          {@code isAnchored} is set to {@code false}
+     * @param y y coordinate of the anchoring touch event, ignored if
+     *          {@code isAnchored} is set to {@code false}
+     * @return {@code true} if one of the above receivers consumed the event,
+     *         {@code false} otherwise
+     */
+    private boolean performLongClickInternal(boolean isAnchored, float x, float y) {
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
 
         boolean handled = false;
-        ListenerInfo li = mListenerInfo;
+        final ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnLongClickListener != null) {
             handled = li.mOnLongClickListener.onLongClick(View.this);
         }
         if (!handled) {
-            handled = showContextMenu();
+            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
         }
         if (handled) {
             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
@@ -10002,32 +10065,36 @@
      * KeyEvent.Callback.onKeyDown()}: perform press of the view
      * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
      * is released, if the view is enabled and clickable.
+     * <p>
+     * Key presses in software keyboards will generally NOT trigger this
+     * listener, although some may elect to do so in some situations. Do not
+     * rely on this to catch software key presses.
      *
-     * <p>Key presses in software keyboards will generally NOT trigger this listener,
-     * although some may elect to do so in some situations. Do not rely on this to
-     * catch software key presses.
-     *
-     * @param keyCode A key code that represents the button pressed, from
-     *                {@link android.view.KeyEvent}.
-     * @param event   The KeyEvent object that defines the button action.
+     * @param keyCode a key code that represents the button pressed, from
+     *                {@link android.view.KeyEvent}
+     * @param event the KeyEvent object that defines the button action
      */
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        boolean result = false;
-
         if (KeyEvent.isConfirmKey(keyCode)) {
             if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                 return true;
             }
-            // Long clickable items don't necessarily have to be clickable
-            if (((mViewFlags & CLICKABLE) == CLICKABLE ||
-                    (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
-                    (event.getRepeatCount() == 0)) {
-                setPressed(true);
-                checkForLongClick(0);
+
+            // Long clickable items don't necessarily have to be clickable.
+            if (((mViewFlags & CLICKABLE) == CLICKABLE
+                    || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+                    && (event.getRepeatCount() == 0)) {
+                // For the purposes of menu anchoring and drawable hotspots,
+                // key events are considered to be at the center of the view.
+                final float x = getWidth() / 2f;
+                final float y = getHeight() / 2f;
+                setPressed(true, x, y);
+                checkForLongClick(0, x, y);
                 return true;
             }
         }
-        return result;
+
+        return false;
     }
 
     /**
@@ -10555,7 +10622,7 @@
                     } else {
                         // Not inside a scrolling container, so show the feedback right away
                         setPressed(true, x, y);
-                        checkForLongClick(0);
+                        checkForLongClick(0, x, y);
                     }
                     break;
 
@@ -12889,6 +12956,10 @@
 
             mPrivateFlags |= PFLAG_DIRTY;
 
+            // Release any resources in-case we don't end up drawing again
+            // as anything cached is no longer valid
+            resetDisplayList();
+
             if (invalidateCache) {
                 mPrivateFlags |= PFLAG_INVALIDATED;
                 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
@@ -19992,13 +20063,14 @@
         }
     }
 
-    private void checkForLongClick(int delayOffset) {
+    private void checkForLongClick(int delayOffset, float x, float y) {
         if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
             mHasPerformedLongPress = false;
 
             if (mPendingCheckForLongPress == null) {
                 mPendingCheckForLongPress = new CheckForLongPress();
             }
+            mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
             postDelayed(mPendingCheckForLongPress,
                     ViewConfiguration.getLongPressTimeout() - delayOffset);
@@ -20997,7 +21069,40 @@
 
     /** @hide */
     public int getPointerShape(MotionEvent event, float x, float y) {
-        return PointerIcon.STYLE_NOT_SPECIFIED;
+        final int value = (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK);
+        switch (value) {
+            case PFLAG3_POINTER_ICON_NOT_SPECIFIED:
+                return PointerIcon.STYLE_NOT_SPECIFIED;
+            case PFLAG3_POINTER_ICON_NULL:
+                return PointerIcon.STYLE_NULL;
+            case PFLAG3_POINTER_ICON_CUSTOM:
+                return PointerIcon.STYLE_CUSTOM;
+            default:
+                return ((value - PFLAG3_POINTER_ICON_VALUE_START) >> PFLAG3_POINTER_ICON_LSHIFT)
+                        + PointerIcon.STYLE_ARROW;
+        }
+    }
+
+    /** @hide */
+    public void setPointerShape(int pointerShape) {
+        int newValue;
+        if (pointerShape == PointerIcon.STYLE_NOT_SPECIFIED) {
+            newValue = PFLAG3_POINTER_ICON_NOT_SPECIFIED;
+        } else if (pointerShape == PointerIcon.STYLE_NULL) {
+            newValue = PFLAG3_POINTER_ICON_NULL;
+        } else if (pointerShape == PointerIcon.STYLE_CUSTOM) {
+            newValue = PFLAG3_POINTER_ICON_CUSTOM;
+        } else if (pointerShape >= PointerIcon.STYLE_ARROW
+                && pointerShape <= PointerIcon.STYLE_GRABBING) {
+            newValue = ((pointerShape - PointerIcon.STYLE_ARROW) << PFLAG3_POINTER_ICON_LSHIFT)
+                    + PFLAG3_POINTER_ICON_VALUE_START;
+        } else {
+            Log.w(VIEW_LOG_TAG, "Invalid pointer shape " + pointerShape + " is specified.");
+            return;
+        }
+        if (newValue != (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK)) {
+            mPrivateFlags3 = (mPrivateFlags3 & ~PFLAG3_POINTER_ICON_MASK) | newValue;
+        }
     }
 
     //
@@ -21354,17 +21459,24 @@
 
     private final class CheckForLongPress implements Runnable {
         private int mOriginalWindowAttachCount;
+        private float mX;
+        private float mY;
 
         @Override
         public void run() {
             if (isPressed() && (mParent != null)
                     && mOriginalWindowAttachCount == mWindowAttachCount) {
-                if (performLongClick()) {
+                if (performLongClick(mX, mY)) {
                     mHasPerformedLongPress = true;
                 }
             }
         }
 
+        public void setAnchor(float x, float y) {
+            mX = x;
+            mY = y;
+        }
+
         public void rememberWindowAttachCount() {
             mOriginalWindowAttachCount = mWindowAttachCount;
         }
@@ -21378,7 +21490,7 @@
         public void run() {
             mPrivateFlags &= ~PFLAG_PREPRESSED;
             setPressed(true, x, y);
-            checkForLongClick(ViewConfiguration.getTapTimeout());
+            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7c7ad91..db978a6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4435,6 +4435,10 @@
                 }
             }
         }
+
+        if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
+            notifyChildOfDragStart(child);
+        }
     }
 
     private void addInArray(View child, int index) {
@@ -4686,6 +4690,10 @@
                 mTransientIndices.set(i, oldIndex - 1);
             }
         }
+
+        if (mCurrentDragStartEvent != null) {
+            mChildrenInterestedInDrag.remove(view);
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7d39a0c..e17bdd7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4191,7 +4191,10 @@
 
                     if (mPointerIconShape != pointerShape) {
                         mPointerIconShape = pointerShape;
-                        event.getDevice().setPointerShape(pointerShape);
+                        final InputDevice inputDevice = event.getDevice();
+                        if (inputDevice != null) {
+                            inputDevice.setPointerShape(pointerShape);
+                        }
                     }
                 } else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
                     mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 1b17736..1735e1b 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3083,8 +3083,32 @@
                 return "ACTION_PASTE";
             case ACTION_SET_SELECTION:
                 return "ACTION_SET_SELECTION";
+            case ACTION_EXPAND:
+                return "ACTION_EXPAND";
+            case ACTION_COLLAPSE:
+                return "ACTION_COLLAPSE";
+            case ACTION_DISMISS:
+                return "ACTION_DISMISS";
+            case ACTION_SET_TEXT:
+                return "ACTION_SET_TEXT";
+            case R.id.accessibilityActionShowOnScreen:
+                return "ACTION_SHOW_ON_SCREEN";
+            case R.id.accessibilityActionScrollToPosition:
+                return "ACTION_SCROLL_TO_POSITION";
+            case R.id.accessibilityActionScrollUp:
+                return "ACTION_SCROLL_UP";
+            case R.id.accessibilityActionScrollLeft:
+                return "ACTION_SCROLL_LEFT";
+            case R.id.accessibilityActionScrollDown:
+                return "ACTION_SCROLL_DOWN";
+            case R.id.accessibilityActionScrollRight:
+                return "ACTION_SCROLL_RIGHT";
+            case R.id.accessibilityActionSetProgress:
+                return "ACTION_SET_PROGRESS";
+            case R.id.accessibilityActionContextClick:
+                return "ACTION_CONTEXT_CLICK";
             default:
-                return"ACTION_UNKNOWN";
+                return "ACTION_UNKNOWN";
         }
     }
 
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 0760d2b..23e9a0d 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -40,6 +40,13 @@
     boolean isForMainFrame();
 
     /**
+     * Gets whether the request was a result of a redirect.
+     *
+     * @return whether the request was a result of a redirect.
+     */
+    boolean isRedirect();
+
+    /**
      * Gets whether a gesture (such as a click) was associated with the request.
      * For security reasons in certain situations this method may return false even though the
      * sequence of events which caused the request to be created was initiated by a user gesture.
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index f2bb55a..0e5034d 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -38,12 +38,42 @@
      * @param url The url to be loaded.
      * @return True if the host application wants to leave the current WebView
      *         and handle the url itself, otherwise return false.
+     * @deprecated Use {@link #shouldOverrideUrlLoading(WebView, WebResourceRequest)
+     *             shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead.
      */
+    @Deprecated
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
         return false;
     }
 
     /**
+     * Give the host application a chance to take over the control when a new
+     * url is about to be loaded in the current WebView. If WebViewClient is not
+     * provided, by default WebView will ask Activity Manager to choose the
+     * proper handler for the url. If WebViewClient is provided, return true
+     * means the host application handles the url, while return false means the
+     * current WebView handles the url.
+     *
+     * <p>Notes:
+     * <ul>
+     * <li>This method is not called for requests using the POST &quot;method&quot;.</li>
+     * <li>This method is also called for subframes with non-http schemes, thus it is
+     * strongly disadvised to unconditionally call {@link WebView#loadUrl(String)}
+     * with the request's url from inside the method and then return true,
+     * as this will make WebView to attempt loading a non-http url, and thus fail.</li>
+     * </ul>
+     * </p>
+     *
+     * @param view The WebView that is initiating the callback.
+     * @param request Object containing the details of the request.
+     * @return True if the host application wants to leave the current WebView
+     *         and handle the url itself, otherwise return false.
+     */
+    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
+        return shouldOverrideUrlLoading(view, request.getUrl().toString());
+    }
+
+    /**
      * Notify the host application that a page has started loading. This method
      * is called once for each main frame load so a page with iframes or
      * framesets will call onPageStarted one time for the main frame. This also
diff --git a/core/java/com/android/internal/alsa/AlsaCardsParser.java b/core/java/com/android/internal/alsa/AlsaCardsParser.java
index 67ee085..17e8c9c 100644
--- a/core/java/com/android/internal/alsa/AlsaCardsParser.java
+++ b/core/java/com/android/internal/alsa/AlsaCardsParser.java
@@ -94,6 +94,12 @@
         public String textFormat() {
           return mCardName + " : " + mCardDescription;
         }
+
+        public void log(int listIndex) {
+            Slog.d(TAG, "" + listIndex +
+                " [" + mCardNum + " " + mCardName + " : " + mCardDescription +
+                " usb:" + mIsUsb);
+        }
     }
 
     public AlsaCardsParser() {}
@@ -169,9 +175,41 @@
 
     // return -1 if none found
     public int getDefaultUsbCard() {
+        // save the current list of devices
+        ArrayList<AlsaCardsParser.AlsaCardRecord> prevRecs = mCardRecords;
+        if (DEBUG) {
+            LogDevices("Previous Devices:", prevRecs);
+        }
+
+        // get the new list of devices
+        scan();
+        if (DEBUG) {
+            LogDevices("Current Devices:", mCardRecords);
+        }
+
+        // Calculate the difference between the old and new device list
+        ArrayList<AlsaCardRecord> newRecs = getNewCardRecords(prevRecs);
+        if (DEBUG) {
+            LogDevices("New Devices:", newRecs);
+        }
+
         // Choose the most-recently added EXTERNAL card
+        // Check recently added devices
+        for (AlsaCardRecord rec : newRecs) {
+            if (DEBUG) {
+                Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb);
+            }
+            if (rec.mIsUsb) {
+                // Found it
+                return rec.mCardNum;
+            }
+        }
+
         // or return the first added EXTERNAL card?
-        for (AlsaCardRecord rec : mCardRecords) {
+        for (AlsaCardRecord rec : prevRecs) {
+            if (DEBUG) {
+                Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb);
+            }
             if (rec.mIsUsb) {
                 return rec.mCardNum;
             }
@@ -183,11 +221,17 @@
     public int getDefaultCard() {
         // return an external card if possible
         int card = getDefaultUsbCard();
+        if (DEBUG) {
+            Slog.d(TAG, "getDefaultCard() default usb card:" + card);
+        }
 
         if (card < 0 && getNumCardRecords() > 0) {
             // otherwise return the (internal) card with the highest number
             card = getCardRecordAt(getNumCardRecords() - 1).mCardNum;
         }
+        if (DEBUG) {
+            Slog.d(TAG, "  returns card:" + card);
+        }
         return card;
     }
 
@@ -222,4 +266,13 @@
             }
         }
     }
+
+    static public void LogDevices(String caption, ArrayList<AlsaCardRecord> deviceList) {
+        Slog.d(TAG, caption + " ----------------");
+        int listIndex = 0;
+        for (AlsaCardRecord device : deviceList) {
+            device.log(listIndex++);
+        }
+        Slog.d(TAG, "----------------");
+    }
 }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index afef763..7fab31f 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -5409,7 +5409,7 @@
      * @Return Returns true if the window should show a non client decor.
      **/
     private static boolean hasNonClientDecor(int workspaceId) {
-        return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID;
+        return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
     }
 
     /**
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 20d00b0..7b69c9e 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -189,6 +189,7 @@
     external/pdfium/core/include/fpdfdoc \
     external/pdfium/fpdfsdk/include \
     external/pdfium/public \
+    external/skia/include/private \
     external/skia/src/core \
     external/skia/src/effects \
     external/skia/src/images \
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 28bc7fe..ecaf951 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -165,7 +165,7 @@
 
     virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
         const SkImageInfo& info = bitmap->info();
-        if (info.fColorType == kUnknown_SkColorType) {
+        if (info.colorType() == kUnknown_SkColorType) {
             ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration");
             return false;
         }
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 93259e7..068517a 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -486,7 +486,7 @@
 android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                              SkColorTable* ctable) {
     const SkImageInfo& info = bitmap->info();
-    if (info.fColorType == kUnknown_SkColorType) {
+    if (info.colorType() == kUnknown_SkColorType) {
         doThrowIAE(env, "unknown bitmap configuration");
         return NULL;
     }
@@ -538,7 +538,7 @@
 
 bool GraphicsJNI::allocatePixels(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable) {
     const SkImageInfo& info = bitmap->info();
-    if (info.fColorType == kUnknown_SkColorType) {
+    if (info.colorType() == kUnknown_SkColorType) {
         doThrowIAE(env, "unknown bitmap configuration");
         return NULL;
     }
@@ -581,7 +581,7 @@
     int fd;
 
     const SkImageInfo& info = bitmap->info();
-    if (info.fColorType == kUnknown_SkColorType) {
+    if (info.colorType() == kUnknown_SkColorType) {
         doThrowIAE(env, "unknown bitmap configuration");
         return nullptr;
     }
@@ -625,7 +625,7 @@
 android::Bitmap* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
         SkColorTable* ctable, int fd, void* addr, bool readOnly) {
     const SkImageInfo& info = bitmap->info();
-    if (info.fColorType == kUnknown_SkColorType) {
+    if (info.colorType() == kUnknown_SkColorType) {
         doThrowIAE(env, "unknown bitmap configuration");
         return nullptr;
     }
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 24055e7..da96b93 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -313,12 +313,11 @@
         return 0;
     }
 
+
     SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
                                          convertPixelFormat(outBuffer.format),
-                                         kPremul_SkAlphaType);
-    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
-        info.fAlphaType = kOpaque_SkAlphaType;
-    }
+                                         outBuffer.format == PIXEL_FORMAT_RGBX_8888 ?
+                                         kOpaque_SkAlphaType : kPremul_SkAlphaType);
 
     SkBitmap bitmap;
     ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 65ebb663..931ad54 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -138,35 +138,36 @@
         return NULL;
     }
 
-    SkImageInfo screenshotInfo;
-    screenshotInfo.fWidth = screenshot->getWidth();
-    screenshotInfo.fHeight = screenshot->getHeight();
-
+    SkColorType colorType;
+    SkAlphaType alphaType;
     switch (screenshot->getFormat()) {
         case PIXEL_FORMAT_RGBX_8888: {
-            screenshotInfo.fColorType = kRGBA_8888_SkColorType;
-            screenshotInfo.fAlphaType = kOpaque_SkAlphaType;
+            colorType = kRGBA_8888_SkColorType;
+            alphaType = kOpaque_SkAlphaType;
             break;
         }
         case PIXEL_FORMAT_RGBA_8888: {
-            screenshotInfo.fColorType = kRGBA_8888_SkColorType;
-            screenshotInfo.fAlphaType = kPremul_SkAlphaType;
+            colorType = kRGBA_8888_SkColorType;
+            alphaType = kPremul_SkAlphaType;
             break;
         }
         case PIXEL_FORMAT_RGB_565: {
-            screenshotInfo.fColorType = kRGB_565_SkColorType;
-            screenshotInfo.fAlphaType = kOpaque_SkAlphaType;
+            colorType = kRGB_565_SkColorType;
+            alphaType = kOpaque_SkAlphaType;
             break;
         }
         default: {
             return NULL;
         }
     }
+    SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(),
+                                                   screenshot->getHeight(),
+                                                   colorType, alphaType);
 
     const size_t rowBytes =
             screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
 
-    if (!screenshotInfo.fWidth || !screenshotInfo.fHeight) {
+    if (!screenshotInfo.width() || !screenshotInfo.height()) {
         return NULL;
     }
 
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index b736a17c..e185281 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -72,29 +72,25 @@
 
 // FIXME: consider exporting this to share (e.g. android_view_Surface.cpp)
 static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) {
-    SkImageInfo info;
-    info.fWidth = buffer.width;
-    info.fHeight = buffer.height;
+    SkColorType colorType = kUnknown_SkColorType;
+    SkAlphaType alphaType = kOpaque_SkAlphaType;
     switch (buffer.format) {
         case WINDOW_FORMAT_RGBA_8888:
-            info.fColorType = kN32_SkColorType;
-            info.fAlphaType = kPremul_SkAlphaType;
+            colorType = kN32_SkColorType;
+            alphaType = kPremul_SkAlphaType;
             break;
         case WINDOW_FORMAT_RGBX_8888:
-            info.fColorType = kN32_SkColorType;
-            info.fAlphaType = kOpaque_SkAlphaType;
+            colorType = kN32_SkColorType;
+            alphaType = kOpaque_SkAlphaType;
             break;
         case WINDOW_FORMAT_RGB_565:
-            info.fColorType = kRGB_565_SkColorType;
-            info.fAlphaType = kOpaque_SkAlphaType;
+            colorType = kRGB_565_SkColorType;
+            alphaType = kOpaque_SkAlphaType;
             break;
         default:
-            info.fColorType = kUnknown_SkColorType;
-            // switch to kUnknown_SkAlphaType when its in skia
-            info.fAlphaType = kOpaque_SkAlphaType;
             break;
     }
-    return info;
+    return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType);
 }
 
 /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 39eda58..5828829 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1476,7 +1476,7 @@
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
-        android:protectionLevel="signature|preinstalled|appop|pre23" />
+        android:protectionLevel="signature|preinstalled|appop|pre23|development" />
 
     <!-- ================================== -->
     <!-- Permissions affecting the system wallpaper -->
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 45e82ac..ff0fa9c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1029,7 +1029,7 @@
     <string name="adb_active_notification_message" msgid="1016654627626476142">"タップしてUSBデバッグを無効化"</string>
     <string name="select_input_method" msgid="8547250819326693584">"キーボードの変更"</string>
     <string name="configure_input_methods" msgid="4769971288371946846">"キーボードの選択"</string>
-    <string name="show_ime" msgid="9157568568695230830">"入力方法を表示する"</string>
+    <string name="show_ime" msgid="9157568568695230830">"スクリーンキーボードを表示する"</string>
     <string name="hardware" msgid="7517821086888990278">"ハードウェア"</string>
     <string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"キーボードレイアウトの選択"</string>
     <string name="select_keyboard_layout_notification_message" msgid="4465907700449257063">"タップしてキーボードレイアウトを選択してください。"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 6c576ad..228f4ae 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -677,7 +677,7 @@
     <string name="lockscreen_transport_stop_description" msgid="5907083260651210034">"Opriți"</string>
     <string name="lockscreen_transport_rew_description" msgid="6944412838651990410">"Derulaţi"</string>
     <string name="lockscreen_transport_ffw_description" msgid="42987149870928985">"Derulaţi rapid înainte"</string>
-    <string name="emergency_calls_only" msgid="6733978304386365407">"Numai apeluri de urgenţă"</string>
+    <string name="emergency_calls_only" msgid="6733978304386365407">"Numai apeluri de urgență"</string>
     <string name="lockscreen_network_locked_message" msgid="143389224986028501">"Rețea blocată"</string>
     <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"Cardul SIM este blocat cu codul PUK."</string>
     <string name="lockscreen_sim_puk_locked_instructions" msgid="8127916255245181063">"Consultaţi Ghidul de utilizare sau contactaţi Serviciul de relaţii cu clienţii."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 960a314..7a3aab8 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1006,14 +1006,14 @@
     <string name="sms_short_code_confirm_never_allow" msgid="446992765774269673">"Asla İzin Verme"</string>
     <string name="sim_removed_title" msgid="6227712319223226185">"SIM kart çıkarıldı"</string>
     <string name="sim_removed_message" msgid="5450336489923274918">"Hücresel ağ, geçerli bir SIM kart takıp cihazınızı yeniden başlatıncaya kadar kullanılamayacak."</string>
-    <string name="sim_done_button" msgid="827949989369963775">"Tamamlandı"</string>
+    <string name="sim_done_button" msgid="827949989369963775">"Bitti"</string>
     <string name="sim_added_title" msgid="3719670512889674693">"SIM kart eklendi"</string>
     <string name="sim_added_message" msgid="7797975656153714319">"Hücresel ağa erişmek için cihazınızı yeniden başlatın."</string>
     <string name="sim_restart_button" msgid="4722407842815232347">"Yeniden başlat"</string>
     <string name="time_picker_dialog_title" msgid="8349362623068819295">"Saati ayarlayın"</string>
     <string name="date_picker_dialog_title" msgid="5879450659453782278">"Tarihi ayarlayın"</string>
     <string name="date_time_set" msgid="5777075614321087758">"Ayarla"</string>
-    <string name="date_time_done" msgid="2507683751759308828">"Tamamlandı"</string>
+    <string name="date_time_done" msgid="2507683751759308828">"Bitti"</string>
     <string name="perms_new_perm_prefix" msgid="8257740710754301407"><font size="12" fgcolor="#ff33b5e5">"YENİ: "</font></string>
     <string name="perms_description_app" msgid="5139836143293299417">"Sağlayan: <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="no_permissions" msgid="7283357728219338112">"İzin gerektirmez"</string>
diff --git a/core/res/res/values-watch/strings.xml b/core/res/res/values-watch/strings.xml
index e5991fc1..dde8b2e 100644
--- a/core/res/res/values-watch/strings.xml
+++ b/core/res/res/values-watch/strings.xml
@@ -26,47 +26,4 @@
 
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. Override from base which says "Body Sensors". [CHAR_LIMIT=25] -->
     <string name="permgrouplab_sensors">Sensors</string>
-
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_contactswear">access your contacts</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_locationwear">access this watch\'s location</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_calendarwear">access your calendar</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_smswear">send and view SMS messages</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_storagewear">access photos, media, and files on your watch</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_microphonewear">record audio</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_camerawear">take pictures and record video</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_phonewear">make and manage phone calls</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permgrouplab_sensorswear">access sensor data about your vital signs</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_statusBarServicewear">be the status bar</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_bodySensorswear">access body sensors (like heart rate monitors)</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_accessFineLocationwear">access precise location (GPS and network-based)</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_accessCoarseLocationwear">access approximate location (network-based)</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_sim_communicationwear">send commands to the SIM</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_createNetworkSocketswear">have full network access</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_manageProfileAndDeviceOwnerswear">manage profile and device owners</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_changeWimaxStatewear">change WiMAX state</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_handoverStatuswear">receive Android Beam transfer status</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_route_media_outputwear">route media output</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_readInstallSessionswear">read install sessions</string>
-    <!-- Description of a  permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
-    <string name="permlab_requestInstallPackageswear">request install packages</string>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 093ea80..d7dd3ec 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5018,6 +5018,12 @@
         <attr name="autoMirrored" format="boolean" />
     </declare-styleable>
 
+    <!-- Drawable class used to wrap other drawables. -->
+    <declare-styleable name="DrawableWrapper">
+        <!-- The wrapped drawable. -->
+        <attr name="drawable" />
+    </declare-styleable>
+
     <!-- Drawable used to render several states. Each state is represented by
          a child drawable. -->
     <declare-styleable name="StateListDrawable">
@@ -5385,6 +5391,7 @@
         <attr name="color" />
     </declare-styleable>
 
+    <!-- Drawable used to wrap and inset another drawable. -->
     <declare-styleable name="InsetDrawable">
         <attr name="visible" />
         <attr name="drawable" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 2621bc9..dd42c22 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -367,7 +367,8 @@
 
     <dimen name="resolver_max_width">480dp</dimen>
 
-    <!-- Size of the offset applied to the position of the circular mask. This
+    <!-- @deprecated Use config_windowOutsetBottom instead.
+         Size of the offset applied to the position of the circular mask. This
          is only used on circular displays. In the case where there is no
          "chin", this will default to 0 -->
     <dimen name="circular_display_mask_offset">0px</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c8d53d7..7584d4d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -621,7 +621,7 @@
     <string name="permdesc_statusBar">Allows the app to disable the status bar or add and remove system icons.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_statusBarService">status bar</string>
+    <string name="permlab_statusBarService">be the status bar</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_statusBarService">Allows the app to be the status bar.</string>
 
@@ -718,7 +718,7 @@
        discover information about which applications are used on the device.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_manageProfileAndDeviceOwners">Manage profile and device owners</string>
+    <string name="permlab_manageProfileAndDeviceOwners">manage profile and device owners</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to set the profile/device owners.
      [CHAR LIMIT=NONE] -->
     <string name="permdesc_manageProfileAndDeviceOwners">Allows apps to set the profile owners and the device owner.</string>
@@ -882,7 +882,7 @@
         Malicious apps may use this to erase or modify your call log.</string>
 
     <!-- Title of the body sensors permission, listed so the user can decide whether to allow the application to access body sensor data. [CHAR LIMIT=30] -->
-    <string name="permlab_bodySensors">body sensors (like heart rate monitors)
+    <string name="permlab_bodySensors">access body sensors (like heart rate monitors)
     </string>
     <!-- Description of the body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
     <string name="permdesc_bodySensors" product="default">Allows the app to access data from sensors
@@ -936,7 +936,7 @@
         with the operation of the GPS or other location sources.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_accessFineLocation">precise location (GPS and
+    <string name="permlab_accessFineLocation">access precise location (GPS and
       network-based)</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessFineLocation">Allows the app to get your
@@ -947,7 +947,7 @@
       battery power.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_accessCoarseLocation">approximate location
+    <string name="permlab_accessCoarseLocation">access approximate location
       (network-based)</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessCoarseLocation">Allows the app to get your
@@ -970,7 +970,7 @@
       without your confirmation.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_sim_communication">sim communication</string>
+    <string name="permlab_sim_communication">send commands to the SIM</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_sim_communication">Allows the app to send commands to the SIM. This is very dangerous.</string>
 
@@ -1077,7 +1077,7 @@
       connected.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_createNetworkSockets">full network access</string>
+    <string name="permlab_createNetworkSockets">have full network access</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_createNetworkSockets">Allows the app to create
      network sockets and use custom network protocols. The browser and other
@@ -1141,7 +1141,7 @@
      WiMAX is enabled and information about any WiMAX networks that are
      connected. </string>
 
-    <string name="permlab_changeWimaxState">Change WiMAX state</string>
+    <string name="permlab_changeWimaxState">change WiMAX state</string>
     <string name="permdesc_changeWimaxState" product="tablet">Allows the app to
       connect the tablet to and disconnect the tablet from WiMAX networks.</string>
     <string name="permdesc_changeWimaxState" product="tv">Allows the app to
@@ -1346,7 +1346,7 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string>
 
-    <string name="permlab_handoverStatus">Receive Android Beam transfer status</string>
+    <string name="permlab_handoverStatus">receive Android Beam transfer status</string>
     <string name="permdesc_handoverStatus">Allows this application to receive information about current Android Beam transfers</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -2975,17 +2975,17 @@
     <string name="activity_list_empty">No matching activities found.</string>
 
     <!-- Title of an application permission that lets an application route media output. -->
-    <string name="permlab_route_media_output">Route media output</string>
+    <string name="permlab_route_media_output">route media output</string>
     <!-- Description of an application permission that lets an application route media output. -->
     <string name="permdesc_route_media_output">Allows an application to route media output to other external devices.</string>
 
     <!-- Title of an application permission that lets it read install sessions. -->
-    <string name="permlab_readInstallSessions">Read install sessions</string>
+    <string name="permlab_readInstallSessions">read install sessions</string>
     <!-- Description of an application permission that lets it read install sessions. -->
     <string name="permdesc_readInstallSessions">Allows an application to read install sessions. This allows it to see details about active package installations.</string>
 
     <!-- Title of an application permission that lets it read install sessions. -->
-    <string name="permlab_requestInstallPackages">Request install packages</string>
+    <string name="permlab_requestInstallPackages">request install packages</string>
     <!-- Description of an application permission that lets it read install sessions. -->
     <string name="permdesc_requestInstallPackages">Allows an application to request installation of packages.</string>
 
diff --git a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
index 80181cf..4c9b00a 100644
--- a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.os.UserHandle;
 import android.test.AndroidTestCase;
+import android.view.inputmethod.InputMethodInfo;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -77,6 +78,27 @@
         assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "sys_app3"), apps);
     }
 
+    public void testQueryAppsDefaultIme() {
+        // Test query default system IMEs
+        List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES,
+                true, UserHandle.of(UserHandle.myUserId()));
+        assertEqualsIgnoreOrder(Arrays.asList("sys_app1"), apps);
+
+        // Test query default IMEs
+        apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
+                UserHandle.of(UserHandle.myUserId()));
+        assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "app4"), apps);
+
+        // Test that GET_DEFAULT_IMES cannot be used with a user id different from current process
+        try {
+            mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
+                    UserHandle.of(UserHandle.USER_NULL));
+            fail("queryApps must fail if wrong user was passed");
+        } catch (IllegalArgumentException e) {
+            // OK
+        }
+    }
+
     private class AppsQueryHelperTestable extends AppsQueryHelper {
 
         public AppsQueryHelperTestable(Context context) {
@@ -121,6 +143,21 @@
             p1.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
             return Arrays.asList(p1);
         }
+
+        @Override
+        protected List<InputMethodInfo> getInputMethodList() {
+            final ResolveInfo sysApp1 = new ResolveInfo();
+            sysApp1.serviceInfo = new ServiceInfo();
+            sysApp1.serviceInfo.packageName = "sys_app1";
+            sysApp1.serviceInfo.name = "name";
+            InputMethodInfo imi1 = new InputMethodInfo(sysApp1, false, null, null, 0, true);
+            final ResolveInfo app4 = new ResolveInfo();
+            app4.serviceInfo = new ServiceInfo();
+            app4.serviceInfo.packageName = "app4";
+            app4.serviceInfo.name = "name";
+            InputMethodInfo imi2 = new InputMethodInfo(app4, false, null, null, 0, true);
+            return Arrays.asList(imi1, imi2);
+        }
     }
 
     private static void assertEqualsIgnoreOrder(List<String> expected, List<String> actual) {
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index b6b4f4f..d5f6321 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -561,9 +561,13 @@
         assertTrue(v46lp.isProvisioned());
 
         assertEquals(ProvisioningChange.STILL_PROVISIONED,
+                LinkProperties.compareProvisioning(v4lp, v46lp));
+        assertEquals(ProvisioningChange.STILL_PROVISIONED,
                 LinkProperties.compareProvisioning(v6lp, v46lp));
         assertEquals(ProvisioningChange.LOST_PROVISIONING,
                 LinkProperties.compareProvisioning(v46lp, v6lp));
+        assertEquals(ProvisioningChange.LOST_PROVISIONING,
+                LinkProperties.compareProvisioning(v46lp, v4lp));
 
         // Check that losing and gaining a secondary router does not change
         // the provisioning status.
diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java
index 74c8e04..2370627 100644
--- a/core/tests/coretests/src/android/util/TimeUtilsTest.java
+++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java
@@ -436,15 +436,17 @@
         assertFormatDuration("+100ms", 100);
         assertFormatDuration("+101ms", 101);
         assertFormatDuration("+330ms", 330);
+        assertFormatDuration("+1s0ms", 1000);
         assertFormatDuration("+1s330ms", 1330);
         assertFormatDuration("+10s24ms", 10024);
+        assertFormatDuration("+1m0s30ms", 60030);
+        assertFormatDuration("+1h0m0s30ms", 3600030);
+        assertFormatDuration("+1d0h0m0s30ms", 86400030);
     }
 
     public void testFormatHugeDuration() {
-        //assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
-        // TODO: improve formatDuration() API
-        assertFormatDuration("+999d23h59m59s999ms", 1342833071555L);
-        assertFormatDuration("-999d23h59m59s999ms", -1342833071555L);
+        assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
+        assertFormatDuration("-15542d1h11m11s555ms", -1342833071555L);
     }
 
     private void assertFormatDuration(String expected, long duration) {
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
new file mode 100644
index 0000000..c5e2ae6
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget;
+
+import static android.widget.espresso.TextViewActions.mouseDragOnText;
+import static android.widget.espresso.TextViewAssertions.hasSelection;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests mouse interaction of the TextView widget from an Activity
+ */
+public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
+
+    public TextViewActivityMouseTest() {
+        super(TextViewActivity.class);
+    }
+
+    @SmallTest
+    public void testSelectTextByDrag() throws Exception {
+        getActivity();
+
+        final String helloWorld = "Hello world!";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+        onView(withId(R.id.textview)).perform(
+                mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+
+        onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+    }
+
+    @SmallTest
+    public void testSelectTextByDrag_reverse() throws Exception {
+        getActivity();
+
+        final String helloWorld = "Hello world!";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+        onView(withId(R.id.textview)).perform(
+                mouseDragOnText( helloWorld.indexOf("ld!"), helloWorld.indexOf("llo")));
+
+        onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
index a0cd848..9ff8e82 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
@@ -20,25 +20,22 @@
 import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static org.hamcrest.Matchers.allOf;
-
 import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.support.test.espresso.UiController;
 import android.support.test.espresso.PerformException;
 import android.support.test.espresso.ViewAction;
 import android.support.test.espresso.action.CoordinatesProvider;
-import android.support.test.espresso.action.GeneralClickAction;
 import android.support.test.espresso.action.MotionEvents;
 import android.support.test.espresso.action.PrecisionDescriber;
-import android.support.test.espresso.action.Press;
 import android.support.test.espresso.action.Swiper;
-import android.support.test.espresso.action.Tap;
 import android.support.test.espresso.util.HumanReadables;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.widget.TextView;
+
 import org.hamcrest.Matcher;
 
 
@@ -51,11 +48,48 @@
  * <ul>
  */
 public final class DragOnTextViewActions implements ViewAction {
+    public interface Dragger extends Swiper {
+        UiController wrapUiController(UiController uiController);
+    }
 
     /**
      * Executes different "drag on text" types to given positions.
      */
-    public enum Drag implements Swiper {
+    public enum Drag implements Dragger {
+
+        /**
+         * Starts a drag with a mouse down.
+         */
+        MOUSE_DOWN {
+            private DownMotionPerformer downMotion = new DownMotionPerformer() {
+                @Override
+                public MotionEvent perform(
+                        UiController uiController, float[] coordinates, float[] precision) {
+                    MotionEvent downEvent = MotionEvents.sendDown(
+                            uiController, coordinates, precision)
+                            .down;
+                    return downEvent;
+                }
+            };
+
+            @Override
+            public Status sendSwipe(
+                    UiController uiController,
+                    float[] startCoordinates, float[] endCoordinates, float[] precision) {
+                return sendLinearDrag(
+                        uiController, downMotion, startCoordinates, endCoordinates, precision);
+            }
+
+            @Override
+            public String toString() {
+                return "mouse down and drag to select";
+            }
+
+            @Override
+            public UiController wrapUiController(UiController uiController) {
+                return new MouseUiController(uiController);
+            }
+        },
 
         /**
          * Starts a drag with a long-press.
@@ -197,6 +231,11 @@
 
             return res;
         }
+
+        @Override
+        public UiController wrapUiController(UiController uiController) {
+            return uiController;
+        }
     }
 
     /**
@@ -215,13 +254,13 @@
         MotionEvent perform(UiController uiController, float[] coordinates, float[] precision);
     }
 
-    private final Swiper mDragger;
+    private final Dragger mDragger;
     private final CoordinatesProvider mStartCoordinatesProvider;
     private final CoordinatesProvider mEndCoordinatesProvider;
     private final PrecisionDescriber mPrecisionDescriber;
 
     public DragOnTextViewActions(
-            Swiper dragger,
+            Dragger dragger,
             CoordinatesProvider startCoordinatesProvider,
             CoordinatesProvider endCoordinatesProvider,
             PrecisionDescriber precisionDescriber) {
@@ -242,6 +281,8 @@
         checkNotNull(uiController);
         checkNotNull(view);
 
+        uiController = mDragger.wrapUiController(uiController);
+
         float[] startCoordinates = mStartCoordinatesProvider.calculateCoordinates(view);
         float[] endCoordinates = mEndCoordinatesProvider.calculateCoordinates(view);
         float[] precision = mPrecisionDescriber.describePrecision();
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
new file mode 100644
index 0000000..f1387f8
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget.espresso;
+
+import android.support.test.espresso.InjectEventSecurityException;
+import android.support.test.espresso.UiController;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Class to wrap an UiController to overwrite source of motion events to SOURCE_MOUSE.
+ * Note that this doesn't change the tool type.
+ */
+public class MouseUiController implements UiController {
+    private final UiController mUiController;
+
+    public MouseUiController(UiController uiController) {
+        mUiController = uiController;
+    }
+
+    @Override
+    public boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException {
+        return mUiController.injectKeyEvent(event);
+    }
+
+    @Override
+    public boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException {
+        // Modify the event to mimic mouse primary button event.
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+        return mUiController.injectMotionEvent(event);
+    }
+
+    @Override
+    public boolean injectString(String str) throws InjectEventSecurityException {
+        return mUiController.injectString(str);
+    }
+
+    @Override
+    public void loopMainThreadForAtLeast(long millisDelay) {
+        mUiController.loopMainThreadForAtLeast(millisDelay);
+    }
+
+    @Override
+    public void loopMainThreadUntilIdle() {
+        mUiController.loopMainThreadUntilIdle();
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 835b1b9..4f5a72b 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -124,6 +124,27 @@
     }
 
     /**
+     * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
+     * TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param startIndex The index of the TextView's text to start a drag from
+     * @param endIndex The index of the TextView's text to end the drag at
+     */
+    public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
+        return actionWithAssertions(
+                new DragOnTextViewActions(
+                        DragOnTextViewActions.Drag.MOUSE_DOWN,
+                        new TextCoordinates(startIndex),
+                        new TextCoordinates(endIndex),
+                        Press.PINPOINT));
+    }
+
+    /**
      * A provider of the x, y coordinates of the text at the specified index in a text view.
      */
     private static final class TextCoordinates implements CoordinatesProvider {
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index 3bd17fa..abcccbd 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -99,7 +99,7 @@
 
     /** @hide */
     public void printShortString(PrintWriter pw) {
-        pw.println("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
+        pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
     }
 
     /**
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index 4fc5ede..971a3a2 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -50,7 +50,7 @@
      * Creates a new animated rotating drawable with no wrapped drawable.
      */
     public AnimatedRotateDrawable() {
-        this(new AnimatedRotateState(null), null);
+        this(new AnimatedRotateState(null, null), null);
     }
 
     @Override
@@ -126,58 +126,22 @@
             @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable);
-        super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
 
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
 
         updateLocalState();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
-        // If we're not waiting on a theme, verify required attributes.
-        if (getDrawable() == null && (mState.mThemeAttrs == null
-                || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
-            throw new XmlPullParserException(a.getPositionDescription()
-                    + ": <animated-rotate> tag requires a 'drawable' attribute or "
-                    + "child tag defining a drawable");
-        }
-    }
-
     @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
 
         final AnimatedRotateState state = mState;
-
-        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
-            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
-            state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
-            state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
-        }
-
-        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
-            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
-            state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
-            state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
-        }
-
-        setFramesCount(a.getInt(
-                R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
-        setFramesDuration(a.getInt(
-                R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
-
-        final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
-    }
-
-    @Override
-    public void applyTheme(@Nullable Theme t) {
-        final AnimatedRotateState state = mState;
         if (state == null) {
             return;
         }
@@ -195,13 +159,49 @@
             }
         }
 
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
-
         updateLocalState();
     }
 
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <animated-rotate> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final AnimatedRotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
+            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
+            state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
+            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
+            state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        setFramesCount(a.getInt(
+                R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
+        setFramesDuration(a.getInt(
+                R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
+    }
+
     public void setFramesCount(int framesCount) {
         mState.mFramesCount = framesCount;
         mIncrement = 360.0f / mState.mFramesCount;
@@ -211,7 +211,15 @@
         mState.mFrameDuration = framesDuration;
     }
 
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new AnimatedRotateState(mState, null);
+        return mState;
+    }
+
     static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         boolean mPivotXRel = false;
         float mPivotX = 0;
         boolean mPivotYRel = false;
@@ -219,8 +227,8 @@
         int mFrameDuration = 150;
         int mFramesCount = 12;
 
-        public AnimatedRotateState(AnimatedRotateState orig) {
-            super(orig);
+        public AnimatedRotateState(AnimatedRotateState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mPivotXRel = orig.mPivotXRel;
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 31fccd0..cdd336d 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -21,6 +21,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
@@ -59,7 +61,7 @@
     private ClipState mState;
 
     ClipDrawable() {
-        this(new ClipState(null), null);
+        this(new ClipState(null, null), null);
     }
 
     /**
@@ -72,7 +74,7 @@
      *                   {@link #VERTICAL}
      */
     public ClipDrawable(Drawable drawable, int gravity, int orientation) {
-        this(new ClipState(null), null);
+        this(new ClipState(null, null), null);
 
         mState.mGravity = gravity;
         mState.mOrientation = orientation;
@@ -81,46 +83,24 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
         super.inflate(r, parser, attrs, theme);
 
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
-        // If we're not waiting on a theme, verify required attributes.
-        if (getDrawable() == null && (mState.mThemeAttrs == null
-                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
-            throw new XmlPullParserException(a.getPositionDescription()
-                    + ": <clip> tag requires a 'drawable' attribute or "
-                    + "child tag defining a drawable");
-        }
-    }
-
     @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
 
         final ClipState state = mState;
-        state.mOrientation = a.getInt(
-                R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
-        state.mGravity = a.getInt(
-                R.styleable.ClipDrawable_gravity, state.mGravity);
-
-        final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
-    }
-
-    @Override
-    public void applyTheme(Theme t) {
-        final ClipState state = mState;
         if (state == null) {
             return;
         }
@@ -136,10 +116,34 @@
                 a.recycle();
             }
         }
+    }
 
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <clip> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final ClipState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mOrientation = a.getInt(
+                R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
+        state.mGravity = a.getInt(
+                R.styleable.ClipDrawable_gravity, state.mGravity);
     }
 
     @Override
@@ -200,12 +204,20 @@
         }
     }
 
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new ClipState(mState, null);
+        return mState;
+    }
+
     static final class ClipState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         int mOrientation = HORIZONTAL;
         int mGravity = Gravity.LEFT;
 
-        ClipState(ClipState orig) {
-            super(orig);
+        ClipState(ClipState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mOrientation = orig.mOrientation;
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index b95c183..ff28777 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -55,6 +55,8 @@
 import java.util.Arrays;
 import java.util.Collection;
 
+import com.android.internal.R;
+
 /**
  * A Drawable is a general abstraction for "something that can be drawn."  Most
  * often you will deal with Drawable as the type of resource retrieved for
@@ -791,8 +793,10 @@
 
     /**
      * Applies the specified theme to this Drawable and its children.
+     *
+     * @param t the theme to apply
      */
-    public void applyTheme(@SuppressWarnings("unused") Theme t) {
+    public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) {
     }
 
     public boolean canApplyTheme() {
@@ -1177,8 +1181,8 @@
      *
      * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme)
      */
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
-            throws XmlPullParserException, IOException {
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
         inflate(r, parser, attrs, null);
     }
 
@@ -1192,17 +1196,11 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
-        final TypedArray a;
-        if (theme != null) {
-            a = theme.obtainStyledAttributes(
-                    attrs, com.android.internal.R.styleable.Drawable, 0, 0);
-        } else {
-            a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable);
-        }
-
-        inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible);
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable);
+        mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible);
         a.recycle();
     }
 
@@ -1212,8 +1210,8 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr)
-            throws XmlPullParserException, IOException {
+    void inflateWithAttributes(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException {
         mVisible = attrs.getBoolean(visibleAttr, mVisible);
     }
 
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index 9185e1a..c427870 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -16,6 +16,8 @@
 
 package android.graphics.drawable;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -23,6 +25,7 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -33,6 +36,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.view.View;
 
 import java.io.IOException;
@@ -112,7 +116,66 @@
         return mDrawable;
     }
 
-    void updateStateFromTypedArray(TypedArray a) {
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final DrawableWrapperState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // The density may have changed since the last update. This will
+        // apply scaling to any existing constant state properties.
+        final int densityDpi = r.getDisplayMetrics().densityDpi;
+        final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(targetDensity);
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        inflateChildDrawable(r, parser, attrs, theme);
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        // If we load the drawable later as part of updating from the typed
+        // array, it will already be themed correctly. So, we can theme the
+        // local drawable first.
+        if (mDrawable != null && mDrawable.canApplyTheme()) {
+            mDrawable.applyTheme(t);
+        }
+
+        final DrawableWrapperState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
+        final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(density);
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.DrawableWrapper);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+    }
+
+    /**
+     * Updates constant state properties from the provided typed array.
+     * <p>
+     * Implementing subclasses should call through to the super method first.
+     *
+     * @param a the typed array rom which properties should be read
+     */
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
         final DrawableWrapperState state = mState;
         if (state == null) {
             return;
@@ -124,20 +187,8 @@
         // Extract the theme attributes, if any.
         state.mThemeAttrs = a.extractThemeAttrs();
 
-        // TODO: Consider using R.styleable.DrawableWrapper_drawable
-    }
-
-    @Override
-    public void applyTheme(Resources.Theme t) {
-        super.applyTheme(t);
-
-        final DrawableWrapperState state = mState;
-        if (state == null) {
-            return;
-        }
-
-        if (mDrawable != null && mDrawable.canApplyTheme()) {
-            mDrawable.applyTheme(t);
+        if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) {
+            setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable));
         }
     }
 
@@ -371,8 +422,9 @@
      * child element will take precedence over any other child elements or
      * explicit drawable attribute.
      */
-    void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs,
-            Resources.Theme theme) throws XmlPullParserException, IOException {
+    private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
         // Seek to the first child element.
         Drawable dr = null;
         int type;
@@ -390,17 +442,61 @@
     }
 
     abstract static class DrawableWrapperState extends Drawable.ConstantState {
-        int[] mThemeAttrs;
+        private int[] mThemeAttrs;
+
         int mChangingConfigurations;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
 
         Drawable.ConstantState mDrawableState;
 
-        DrawableWrapperState(DrawableWrapperState orig) {
+        DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
             if (orig != null) {
                 mThemeAttrs = orig.mThemeAttrs;
                 mChangingConfigurations = orig.mChangingConfigurations;
                 mDrawableState = orig.mDrawableState;
             }
+
+            final int density;
+            if (res != null) {
+                density = res.getDisplayMetrics().densityDpi;
+            } else if (orig != null) {
+                density = orig.mDensity;
+            } else {
+                density = 0;
+            }
+
+            mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
+        }
+
+        /**
+         * Sets the constant state density.
+         * <p>
+         * If the density has been previously set, dispatches the change to
+         * subclasses so that density-dependent properties may be scaled as
+         * necessary.
+         *
+         * @param targetDensity the new constant state density
+         */
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                onDensityChanged(sourceDensity, targetDensity);
+            }
+        }
+
+        /**
+         * Called when the constant state density changes.
+         * <p>
+         * Subclasses with density-dependent constant state properties should
+         * override this method and scale their properties as necessary.
+         *
+         * @param sourceDensity the previous constant state density
+         * @param targetDensity the new constant state density
+         */
+        void onDensityChanged(int sourceDensity, int targetDensity) {
+            // Stub method.
         }
 
         @Override
@@ -425,7 +521,7 @@
         }
 
         @Override
-        public abstract Drawable newDrawable(Resources res);
+        public abstract Drawable newDrawable(@Nullable Resources res);
 
         @Override
         public int getChangingConfigurations() {
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index e1ebdbb..927b9c9 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -22,14 +22,17 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Outline;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 
 import java.io.IOException;
 
@@ -58,7 +61,7 @@
      * No-arg constructor used by drawable inflation.
      */
     InsetDrawable() {
-        this(new InsetState(null), null);
+        this(new InsetState(null, null), null);
     }
 
     /**
@@ -67,7 +70,7 @@
      * @param drawable The drawable to inset.
      * @param inset Inset in pixels around the drawable.
      */
-    public InsetDrawable(Drawable drawable, int inset) {
+    public InsetDrawable(@Nullable Drawable drawable, int inset) {
         this(drawable, inset, inset, inset, inset);
     }
 
@@ -80,9 +83,9 @@
      * @param insetRight Right inset in pixels.
      * @param insetBottom Bottom inset in pixels.
      */
-    public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight,
-            int insetBottom) {
-        this(new InsetState(null), null);
+    public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop,
+            int insetRight, int insetBottom) {
+        this(new InsetState(null, null), null);
 
         mState.mInsetLeft = insetLeft;
         mState.mInsetTop = insetTop;
@@ -93,70 +96,24 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
         super.inflate(r, parser, attrs, theme);
 
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
-        // If we're not waiting on a theme, verify required attributes.
-        if (getDrawable() == null && (mState.mThemeAttrs == null
-                || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
-            throw new XmlPullParserException(a.getPositionDescription()
-                    + ": <inset> tag requires a 'drawable' attribute or "
-                    + "child tag defining a drawable");
-        }
-    }
-
     @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
 
         final InsetState state = mState;
-        final int N = a.getIndexCount();
-        for (int i = 0; i < N; i++) {
-            final int attr = a.getIndex(i);
-            switch (attr) {
-                case R.styleable.InsetDrawable_drawable:
-                    final Drawable dr = a.getDrawable(attr);
-                    if (dr != null) {
-                        setDrawable(dr);
-                    }
-                    break;
-                case R.styleable.InsetDrawable_inset:
-                    final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE);
-                    if (inset != Integer.MIN_VALUE) {
-                        state.mInsetLeft = inset;
-                        state.mInsetTop = inset;
-                        state.mInsetRight = inset;
-                        state.mInsetBottom = inset;
-                    }
-                    break;
-                case R.styleable.InsetDrawable_insetLeft:
-                    state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft);
-                    break;
-                case R.styleable.InsetDrawable_insetTop:
-                    state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop);
-                    break;
-                case R.styleable.InsetDrawable_insetRight:
-                    state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight);
-                    break;
-                case R.styleable.InsetDrawable_insetBottom:
-                    state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom);
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public void applyTheme(Theme t) {
-        final InsetState state = mState;
         if (state == null) {
             return;
         }
@@ -172,10 +129,47 @@
                 a.recycle();
             }
         }
+    }
 
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <inset> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final InsetState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        // Inset attribute may be overridden by more specific attributes.
+        if (a.hasValue(R.styleable.InsetDrawable_inset)) {
+            final int inset = a.getDimensionPixelOffset(R.styleable.InsetDrawable_inset, 0);
+            state.mInsetLeft = inset;
+            state.mInsetTop = inset;
+            state.mInsetRight = inset;
+            state.mInsetBottom = inset;
+        }
+
+        state.mInsetLeft = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetLeft, state.mInsetLeft);
+        state.mInsetRight = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetRight, state.mInsetRight);
+        state.mInsetTop = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetTop, state.mInsetTop);
+        state.mInsetBottom = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetBottom, state.mInsetBottom);
     }
 
     @Override
@@ -243,30 +237,72 @@
 
     @Override
     DrawableWrapperState mutateConstantState() {
-        mState = new InsetState(mState);
+        mState = new InsetState(mState, null);
         return mState;
     }
 
     static final class InsetState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         int mInsetLeft = 0;
         int mInsetTop = 0;
         int mInsetRight = 0;
         int mInsetBottom = 0;
 
-        InsetState(InsetState orig) {
-            super(orig);
+        InsetState(@Nullable InsetState orig, @Nullable Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mInsetLeft = orig.mInsetLeft;
                 mInsetTop = orig.mInsetTop;
                 mInsetRight = orig.mInsetRight;
                 mInsetBottom = orig.mInsetBottom;
+
+                if (orig.mDensity != mDensity) {
+                    applyDensityScaling(orig.mDensity, mDensity);
+                }
             }
         }
 
         @Override
-        public Drawable newDrawable(Resources res) {
-            return new InsetDrawable(this, res);
+        void onDensityChanged(int sourceDensity, int targetDensity) {
+            super.onDensityChanged(sourceDensity, targetDensity);
+
+            applyDensityScaling(sourceDensity, targetDensity);
+        }
+
+        /**
+         * Called when the constant state density changes to scale
+         * density-dependent properties specific to insets.
+         *
+         * @param sourceDensity the previous constant state density
+         * @param targetDensity the new constant state density
+         */
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mInsetLeft = Bitmap.scaleFromDensity(mInsetLeft, sourceDensity, targetDensity);
+            mInsetTop = Bitmap.scaleFromDensity(mInsetTop, sourceDensity, targetDensity);
+            mInsetRight = Bitmap.scaleFromDensity(mInsetRight, sourceDensity, targetDensity);
+            mInsetBottom = Bitmap.scaleFromDensity(mInsetBottom, sourceDensity, targetDensity);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            // If this drawable is being created for a different density,
+            // just create a new constant state and call it a day.
+            final InsetState state;
+            if (res != null) {
+                final int densityDpi = res.getDisplayMetrics().densityDpi;
+                final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+                if (density != mDensity) {
+                    state = new InsetState(this, res);
+                } else {
+                    state = this;
+                }
+            } else {
+                state = this;
+            }
+
+            return new InsetDrawable(state, res);
         }
     }
 
@@ -274,7 +310,7 @@
      * The one constructor to rule them all. This is called by all public
      * constructors to set the state and initialize local properties.
      */
-    private InsetDrawable(InsetState state, Resources res) {
+    private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) {
         super(state, res);
 
         mState = state;
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 1a0ba6f..4368fe9 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -30,6 +30,7 @@
 import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.LayoutDirection;
 import android.view.Gravity;
 import android.view.View;
@@ -124,7 +125,7 @@
         final int length = layers.length;
         final ChildDrawable[] r = new ChildDrawable[length];
         for (int i = 0; i < length; i++) {
-            r[i] = new ChildDrawable();
+            r[i] = new ChildDrawable(mLayerState.mDensity);
             r[i].mDrawable = layers[i];
             layers[i].setCallback(this);
             mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
@@ -140,6 +141,10 @@
         this((LayerState) null, null);
     }
 
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
     LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
         mLayerState = createConstantState(state, res);
         if (mLayerState.mNum > 0) {
@@ -153,24 +158,137 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         super.inflate(r, parser, attrs, theme);
 
+        final LayerState state = mLayerState;
+        if (state == null) {
+            return;
+        }
+
+        // The density may have changed since the last update. This will
+        // apply scaling to any existing constant state properties.
+        final int densityDpi = r.getDisplayMetrics().densityDpi;
+        final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(density);
+
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
         updateStateFromTypedArray(a);
         a.recycle();
 
+        final ChildDrawable[] array = state.mChildren;
+        final int N = state.mNum;
+        for (int i = 0; i < N; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(density);
+        }
+
         inflateLayers(r, parser, attrs, theme);
 
         ensurePadding();
         refreshPadding();
     }
 
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final LayerState state = mLayerState;
+        if (state == null) {
+            return;
+        }
+
+        final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
+        final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(density);
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.LayerDrawable);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        final ChildDrawable[] array = state.mChildren;
+        final int N = state.mNum;
+        for (int i = 0; i < N; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(density);
+
+            if (layer.mThemeAttrs != null) {
+                final TypedArray a = t.resolveAttributes(
+                        layer.mThemeAttrs, R.styleable.LayerDrawableItem);
+                updateLayerFromTypedArray(layer, a);
+                a.recycle();
+            }
+
+            final Drawable d = layer.mDrawable;
+            if (d != null && d.canApplyTheme()) {
+                d.applyTheme(t);
+
+                // Update cached mask of child changing configurations.
+                state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
+            }
+        }
+    }
+
+    /**
+     * Inflates child layers using the specified parser.
+     */
+    private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final LayerState state = mLayerState;
+
+        final int innerDepth = parser.getDepth() + 1;
+        int type;
+        int depth;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (depth > innerDepth || !parser.getName().equals("item")) {
+                continue;
+            }
+
+            final ChildDrawable layer = new ChildDrawable(state.mDensity);
+            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
+            updateLayerFromTypedArray(layer, a);
+            a.recycle();
+
+            // If the layer doesn't have a drawable or unresolved theme
+            // attribute for a drawable, attempt to parse one from the child
+            // element.
+            if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
+                    layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
+                while ((type = parser.next()) == XmlPullParser.TEXT) {
+                }
+                if (type != XmlPullParser.START_TAG) {
+                    throw new XmlPullParserException(parser.getPositionDescription()
+                            + ": <item> tag requires a 'drawable' attribute or "
+                            + "child tag defining a drawable");
+                }
+                layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
+            }
+
+            if (layer.mDrawable != null) {
+                state.mChildrenChangingConfigurations |=
+                        layer.mDrawable.getChangingConfigurations();
+                layer.mDrawable.setCallback(this);
+            }
+
+            addLayer(layer);
+        }
+    }
+
     /**
      * Initializes the constant state from the values in the typed array.
      */
-    private void updateStateFromTypedArray(TypedArray a) {
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
         final LayerState state = mLayerState;
 
         // Account for any configuration changes.
@@ -181,7 +299,7 @@
 
         final int N = a.getIndexCount();
         for (int i = 0; i < N; i++) {
-            int attr = a.getIndex(i);
+            final int attr = a.getIndex(i);
             switch (attr) {
                 case R.styleable.LayerDrawable_opacity:
                     state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride);
@@ -214,57 +332,7 @@
         }
     }
 
-    /**
-     * Inflates child layers using the specified parser.
-     */
-    private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
-            throws XmlPullParserException, IOException {
-        final LayerState state = mLayerState;
-
-        final int innerDepth = parser.getDepth() + 1;
-        int type;
-        int depth;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
-            if (type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            if (depth > innerDepth || !parser.getName().equals("item")) {
-                continue;
-            }
-
-            final ChildDrawable layer = new ChildDrawable();
-            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
-            updateLayerFromTypedArray(layer, a);
-            a.recycle();
-
-            // If the layer doesn't have a drawable or unresolved theme
-            // attribute for a drawable, attempt to parse one from the child
-            // element.
-            if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
-                    layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
-                while ((type = parser.next()) == XmlPullParser.TEXT) {
-                }
-                if (type != XmlPullParser.START_TAG) {
-                    throw new XmlPullParserException(parser.getPositionDescription()
-                            + ": <item> tag requires a 'drawable' attribute or "
-                            + "child tag defining a drawable");
-                }
-                layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
-            }
-
-            if (layer.mDrawable != null) {
-                state.mChildrenChangingConfigurations |=
-                        layer.mDrawable.getChangingConfigurations();
-                layer.mDrawable.setCallback(this);
-            }
-
-            addLayer(layer);
-        }
-    }
-
-    private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) {
+    private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
         final LayerState state = mLayerState;
 
         // Account for any configuration changes.
@@ -273,25 +341,42 @@
         // Extract the theme attributes, if any.
         layer.mThemeAttrs = a.extractThemeAttrs();
 
-        layer.mInsetL = a.getDimensionPixelOffset(
-                R.styleable.LayerDrawableItem_left, layer.mInsetL);
-        layer.mInsetT = a.getDimensionPixelOffset(
-                R.styleable.LayerDrawableItem_top, layer.mInsetT);
-        layer.mInsetR = a.getDimensionPixelOffset(
-                R.styleable.LayerDrawableItem_right, layer.mInsetR);
-        layer.mInsetB = a.getDimensionPixelOffset(
-                R.styleable.LayerDrawableItem_bottom, layer.mInsetB);
-        layer.mInsetS = a.getDimensionPixelOffset(
-                R.styleable.LayerDrawableItem_start, layer.mInsetS);
-        layer.mInsetE = a.getDimensionPixelOffset(
-                R.styleable.LayerDrawableItem_end, layer.mInsetE);
-        layer.mWidth = a.getDimensionPixelSize(
-                R.styleable.LayerDrawableItem_width, layer.mWidth);
-        layer.mHeight = a.getDimensionPixelSize(
-                R.styleable.LayerDrawableItem_height, layer.mHeight);
-        layer.mGravity = a.getInteger(
-                R.styleable.LayerDrawableItem_gravity, layer.mGravity);
-        layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId);
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            final int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.LayerDrawableItem_left:
+                    layer.mInsetL = a.getDimensionPixelOffset(attr, layer.mInsetL);
+                    break;
+                case R.styleable.LayerDrawableItem_top:
+                    layer.mInsetT = a.getDimensionPixelOffset(attr, layer.mInsetT);
+                    break;
+                case R.styleable.LayerDrawableItem_right:
+                    layer.mInsetR = a.getDimensionPixelOffset(attr, layer.mInsetR);
+                    break;
+                case R.styleable.LayerDrawableItem_bottom:
+                    layer.mInsetB = a.getDimensionPixelOffset(attr, layer.mInsetB);
+                    break;
+                case R.styleable.LayerDrawableItem_start:
+                    layer.mInsetS = a.getDimensionPixelOffset(attr, layer.mInsetS);
+                    break;
+                case R.styleable.LayerDrawableItem_end:
+                    layer.mInsetE = a.getDimensionPixelOffset(attr, layer.mInsetE);
+                    break;
+                case R.styleable.LayerDrawableItem_width:
+                    layer.mWidth = a.getDimensionPixelSize(attr, layer.mWidth);
+                    break;
+                case R.styleable.LayerDrawableItem_height:
+                    layer.mHeight = a.getDimensionPixelSize(attr, layer.mHeight);
+                    break;
+                case R.styleable.LayerDrawableItem_gravity:
+                    layer.mGravity = a.getInteger(attr, layer.mGravity);
+                    break;
+                case R.styleable.LayerDrawableItem_id:
+                    layer.mId = a.getResourceId(attr, layer.mId);
+                    break;
+            }
+        }
 
         final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
         if (dr != null) {
@@ -300,44 +385,6 @@
     }
 
     @Override
-    public void applyTheme(Theme t) {
-        super.applyTheme(t);
-
-        final LayerState state = mLayerState;
-        if (state == null) {
-            return;
-        }
-
-        if (state.mThemeAttrs != null) {
-            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable);
-            updateStateFromTypedArray(a);
-            a.recycle();
-        }
-
-        final ChildDrawable[] array = state.mChildren;
-        final int N = state.mNum;
-        for (int i = 0; i < N; i++) {
-            final ChildDrawable layer = array[i];
-            if (layer.mThemeAttrs != null) {
-                final TypedArray a = t.resolveAttributes(layer.mThemeAttrs,
-                        R.styleable.LayerDrawableItem);
-                updateLayerFromTypedArray(layer, a);
-                a.recycle();
-            }
-
-            final Drawable d = layer.mDrawable;
-            if (d != null && d.canApplyTheme()) {
-                d.applyTheme(t);
-
-                // Update cached mask of child changing configurations.
-                state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
-            }
-        }
-
-        ensurePadding();
-    }
-
-    @Override
     public boolean canApplyTheme() {
         return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
     }
@@ -368,7 +415,7 @@
      * @param layer The layer to add.
      * @return The index of the layer.
      */
-    int addLayer(ChildDrawable layer) {
+    int addLayer(@NonNull ChildDrawable layer) {
         final LayerState st = mLayerState;
         final int N = st.mChildren != null ? st.mChildren.length : 0;
         final int i = st.mNum;
@@ -418,7 +465,7 @@
     }
 
     private ChildDrawable createLayer(Drawable dr) {
-        final ChildDrawable layer = new ChildDrawable();
+        final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
         layer.mDrawable = dr;
         return layer;
     }
@@ -1708,6 +1755,7 @@
     static class ChildDrawable {
         public Drawable mDrawable;
         public int[] mThemeAttrs;
+        public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
         public int mInsetL, mInsetT, mInsetR, mInsetB;
         public int mInsetS = UNDEFINED_INSET;
         public int mInsetE = UNDEFINED_INSET;
@@ -1716,11 +1764,12 @@
         public int mGravity = Gravity.NO_GRAVITY;
         public int mId = View.NO_ID;
 
-        ChildDrawable() {
-            // Default empty constructor.
+        ChildDrawable(int density) {
+            mDensity = density;
         }
 
-        ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) {
+        ChildDrawable(@NonNull ChildDrawable orig, @NonNull LayerDrawable owner,
+                @Nullable Resources res) {
             final Drawable dr = orig.mDrawable;
             final Drawable clone;
             if (dr != null) {
@@ -1750,19 +1799,58 @@
             mHeight = orig.mHeight;
             mGravity = orig.mGravity;
             mId = orig.mId;
+
+            final int densityDpi = res == null ? orig.mDensity : res.getDisplayMetrics().densityDpi;
+            mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+
+            if (orig.mDensity != mDensity) {
+                applyDensityScaling(orig.mDensity, mDensity);
+            }
         }
 
         public boolean canApplyTheme() {
             return mThemeAttrs != null
                     || (mDrawable != null && mDrawable.canApplyTheme());
         }
+
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                applyDensityScaling(sourceDensity, targetDensity);
+            }
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mInsetL = Bitmap.scaleFromDensity(mInsetL, sourceDensity, targetDensity);
+            mInsetT = Bitmap.scaleFromDensity(mInsetT, sourceDensity, targetDensity);
+            mInsetR = Bitmap.scaleFromDensity(mInsetR, sourceDensity, targetDensity);
+            mInsetB = Bitmap.scaleFromDensity(mInsetB, sourceDensity, targetDensity);
+            if (mInsetS != UNDEFINED_INSET) {
+                mInsetS = Bitmap.scaleFromDensity(mInsetS, sourceDensity, targetDensity);
+            }
+            if (mInsetE != UNDEFINED_INSET) {
+                mInsetE = Bitmap.scaleFromDensity(mInsetE, sourceDensity, targetDensity);
+            }
+            if (mWidth > 0) {
+                mWidth = Bitmap.scaleFromDensity(mWidth, sourceDensity, targetDensity);
+            }
+            if (mHeight > 0) {
+                mHeight = Bitmap.scaleFromDensity(mHeight, sourceDensity, targetDensity);
+            }
+        }
     }
 
     static class LayerState extends ConstantState {
+        private int[] mThemeAttrs;
+
         int mNum;
         ChildDrawable[] mChildren;
-        int[] mThemeAttrs;
 
+        int mDensity;
+
+        // These values all correspond to mDensity.
         int mPaddingTop = -1;
         int mPaddingBottom = -1;
         int mPaddingLeft = -1;
@@ -1784,7 +1872,19 @@
 
         private int mPaddingMode = PADDING_MODE_NEST;
 
-        LayerState(LayerState orig, LayerDrawable owner, Resources res) {
+        LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner,
+                @Nullable Resources res) {
+            final int densityDpi;
+            if (res != null) {
+                densityDpi = res.getDisplayMetrics().densityDpi;
+            } else if (orig != null) {
+                densityDpi = orig.mDensity;
+            } else {
+                densityDpi = 0;
+            }
+
+            mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+
             if (orig != null) {
                 final ChildDrawable[] origChildDrawable = orig.mChildren;
                 final int N = orig.mNum;
@@ -1814,12 +1914,56 @@
                 mPaddingStart = orig.mPaddingStart;
                 mPaddingEnd = orig.mPaddingEnd;
                 mOpacityOverride = orig.mOpacityOverride;
+
+                if (orig.mDensity != mDensity) {
+                    applyDensityScaling(orig.mDensity, mDensity);
+                }
             } else {
                 mNum = 0;
                 mChildren = null;
             }
         }
 
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                onDensityChanged(sourceDensity, targetDensity);
+            }
+        }
+
+        protected void onDensityChanged(int sourceDensity, int targetDensity) {
+            applyDensityScaling(sourceDensity, targetDensity);
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            if (mPaddingLeft > 0) {
+                mPaddingLeft = Bitmap.scaleFromDensity(
+                        mPaddingLeft, sourceDensity, targetDensity);
+            }
+            if (mPaddingTop > 0) {
+                mPaddingTop = Bitmap.scaleFromDensity(
+                        mPaddingTop, sourceDensity, targetDensity);
+            }
+            if (mPaddingRight > 0) {
+                mPaddingRight = Bitmap.scaleFromDensity(
+                        mPaddingRight, sourceDensity, targetDensity);
+            }
+            if (mPaddingBottom > 0) {
+                mPaddingBottom = Bitmap.scaleFromDensity(
+                        mPaddingBottom, sourceDensity, targetDensity);
+            }
+            if (mPaddingStart > 0) {
+                mPaddingStart = Bitmap.scaleFromDensity(
+                        mPaddingStart, sourceDensity, targetDensity);
+            }
+            if (mPaddingEnd > 0) {
+                mPaddingEnd = Bitmap.scaleFromDensity(
+                        mPaddingEnd, sourceDensity, targetDensity);
+            }
+        }
+
         @Override
         public boolean canApplyTheme() {
             if (mThemeAttrs != null || super.canApplyTheme()) {
@@ -1844,7 +1988,7 @@
         }
 
         @Override
-        public Drawable newDrawable(Resources res) {
+        public Drawable newDrawable(@Nullable Resources res) {
             return new LayerDrawable(this, res);
         }
 
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 2690223..a196fad 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -409,18 +409,20 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RippleDrawable);
-        updateStateFromTypedArray(a);
-        a.recycle();
 
         // Force padding default to STACK before inflating.
         setPaddingMode(PADDING_MODE_STACK);
 
+        // Inflation will advance the XmlPullParser and AttributeSet.
         super.inflate(r, parser, attrs, theme);
 
-        setTargetDensity(r.getDisplayMetrics());
+        updateStateFromTypedArray(a);
+        verifyRequiredAttributes(a);
+        a.recycle();
 
         updateLocalState();
     }
@@ -461,7 +463,7 @@
     /**
      * Initializes the constant state from the values in the typed array.
      */
-    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
+    private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
         final RippleState state = mState;
 
         // Account for any configuration changes.
@@ -477,11 +479,9 @@
 
         mState.mMaxRadius = a.getDimensionPixelSize(
                 R.styleable.RippleDrawable_radius, mState.mMaxRadius);
-
-        verifyRequiredAttributes(a);
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
         if (mState.mColor == null && (mState.mTouchThemeAttrs == null
                 || mState.mTouchThemeAttrs[R.styleable.RippleDrawable_color] == 0)) {
             throw new XmlPullParserException(a.getPositionDescription() +
@@ -489,20 +489,8 @@
         }
     }
 
-    /**
-     * Set the density at which this drawable will be rendered.
-     *
-     * @param metrics The display metrics for this drawable.
-     */
-    private void setTargetDensity(DisplayMetrics metrics) {
-        if (mDensity != metrics.density) {
-            mDensity = metrics.density;
-            invalidateSelf(false);
-        }
-    }
-
     @Override
-    public void applyTheme(Theme t) {
+    public void applyTheme(@NonNull Theme t) {
         super.applyTheme(t);
 
         final RippleState state = mState;
@@ -515,6 +503,7 @@
                     R.styleable.RippleDrawable);
             try {
                 updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
             } catch (XmlPullParserException e) {
                 throw new RuntimeException(e);
             } finally {
@@ -555,7 +544,8 @@
             mBackground = new RippleBackground(this, mHotspotBounds, mForceSoftware);
         }
 
-        mBackground.setup(mState.mMaxRadius, mDensity);
+        final float densityScale = mState.mDensity * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        mBackground.setup(mState.mMaxRadius, densityScale);
         mBackground.enter(focused);
     }
 
@@ -1002,6 +992,23 @@
                 mTouchThemeAttrs = origs.mTouchThemeAttrs;
                 mColor = origs.mColor;
                 mMaxRadius = origs.mMaxRadius;
+
+                if (origs.mDensity != mDensity) {
+                    applyDensityScaling(orig.mDensity, mDensity);
+                }
+            }
+        }
+
+        @Override
+        protected void onDensityChanged(int sourceDensity, int targetDensity) {
+            super.onDensityChanged(sourceDensity, targetDensity);
+
+            applyDensityScaling(sourceDensity, targetDensity);
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            if (mMaxRadius != RADIUS_AUTO) {
+                mMaxRadius = Bitmap.scaleFromDensity(mMaxRadius, sourceDensity, targetDensity);
             }
         }
 
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index 036a078..1531ba2 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -21,6 +21,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.content.res.Resources;
@@ -58,22 +60,46 @@
      * Creates a new rotating drawable with no wrapped drawable.
      */
     public RotateDrawable() {
-        this(new RotateState(null), null);
+        this(new RotateState(null, null), null);
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable);
-        super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
 
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final RotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                throw new RuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
         // If we're not waiting on a theme, verify required attributes.
         if (getDrawable() == null && (mState.mThemeAttrs == null
                 || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) {
@@ -83,11 +109,14 @@
         }
     }
 
-    @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
-
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
         final RotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
 
         // Extract the theme attributes, if any.
         state.mThemeAttrs = a.extractThemeAttrs();
@@ -109,35 +138,6 @@
         state.mToDegrees = a.getFloat(
                 R.styleable.RotateDrawable_toDegrees, state.mToDegrees);
         state.mCurrentDegrees = state.mFromDegrees;
-
-        final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
-    }
-
-    @Override
-    public void applyTheme(Theme t) {
-        final RotateState state = mState;
-        if (state == null) {
-            return;
-        }
-
-        if (state.mThemeAttrs != null) {
-            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
-            try {
-                updateStateFromTypedArray(a);
-                verifyRequiredAttributes(a);
-            } catch (XmlPullParserException e) {
-                throw new RuntimeException(e);
-            } finally {
-                a.recycle();
-            }
-        }
-
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
     }
 
     @Override
@@ -316,11 +316,13 @@
 
     @Override
     DrawableWrapperState mutateConstantState() {
-        mState = new RotateState(mState);
+        mState = new RotateState(mState, null);
         return mState;
     }
 
     static final class RotateState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         boolean mPivotXRel = true;
         float mPivotX = 0.5f;
         boolean mPivotYRel = true;
@@ -329,8 +331,8 @@
         float mToDegrees = 360.0f;
         float mCurrentDegrees = 0.0f;
 
-        RotateState(RotateState orig) {
-            super(orig);
+        RotateState(RotateState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mPivotXRel = orig.mPivotXRel;
diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java
index f9206b7..f87c19a 100644
--- a/graphics/java/android/graphics/drawable/ScaleDrawable.java
+++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java
@@ -21,6 +21,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
@@ -67,7 +69,7 @@
     private ScaleState mState;
 
     ScaleDrawable() {
-        this(new ScaleState(null), null);
+        this(new ScaleState(null, null), null);
     }
 
     /**
@@ -83,7 +85,7 @@
      *                    is at the maximum value, or -1 to not scale height
      */
     public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
-        this(new ScaleState(null), null);
+        this(new ScaleState(null, null), null);
 
         mState.mGravity = gravity;
         mState.mScaleWidth = scaleWidth;
@@ -93,20 +95,46 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
         super.inflate(r, parser, attrs, theme);
 
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
 
         updateLocalState();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final ScaleState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                throw new RuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        updateLocalState();
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
         // If we're not waiting on a theme, verify required attributes.
         if (getDrawable() == null && (mState.mThemeAttrs == null
                 || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
@@ -116,11 +144,18 @@
         }
     }
 
-    @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
-
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
         final ScaleState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
         state.mScaleWidth = getPercent(a,
                 R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth);
         state.mScaleHeight = getPercent(a,
@@ -131,11 +166,6 @@
                 R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin);
         state.mInitialLevel = a.getInt(
                 R.styleable.ScaleDrawable_level, state.mInitialLevel);
-
-        final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
     }
 
     private static float getPercent(TypedArray a, int index, float defaultValue) {
@@ -157,33 +187,6 @@
     }
 
     @Override
-    public void applyTheme(Theme t) {
-        final ScaleState state = mState;
-        if (state == null) {
-            return;
-        }
-
-        if (state.mThemeAttrs != null) {
-            final TypedArray a = t.resolveAttributes(
-                    state.mThemeAttrs, R.styleable.ScaleDrawable);
-            try {
-                updateStateFromTypedArray(a);
-                verifyRequiredAttributes(a);
-            } catch (XmlPullParserException e) {
-                throw new RuntimeException(e);
-            } finally {
-                a.recycle();
-            }
-        }
-
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
-
-        updateLocalState();
-    }
-
-    @Override
     public void draw(Canvas canvas) {
         final Drawable d = getDrawable();
         if (d != null && d.getLevel() != 0) {
@@ -243,7 +246,7 @@
 
     @Override
     DrawableWrapperState mutateConstantState() {
-        mState = new ScaleState(mState);
+        mState = new ScaleState(mState, null);
         return mState;
     }
 
@@ -251,14 +254,16 @@
         /** Constant used to disable scaling for a particular dimension. */
         private static final float DO_NOT_SCALE = -1.0f;
 
+        private int[] mThemeAttrs;
+
         float mScaleWidth = DO_NOT_SCALE;
         float mScaleHeight = DO_NOT_SCALE;
         int mGravity = Gravity.LEFT;
         boolean mUseIntrinsicSizeAsMin = false;
         int mInitialLevel = 0;
 
-        ScaleState(ScaleState orig) {
-            super(orig);
+        ScaleState(ScaleState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mScaleWidth = orig.mScaleWidth;
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4385e70..d94c91d 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -113,6 +113,7 @@
 endef
 
 hwui_c_includes += \
+    external/skia/include/private \
     external/skia/src/core
 
 hwui_shared_libraries := \
@@ -202,6 +203,7 @@
     unit_tests/CanvasStateTests.cpp \
     unit_tests/ClipAreaTests.cpp \
     unit_tests/DamageAccumulatorTests.cpp \
+    unit_tests/FatVectorTests.cpp \
     unit_tests/LinearAllocatorTests.cpp \
     unit_tests/StringUtilsTests.cpp
 
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 94806ca..0868853 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -25,112 +25,210 @@
 namespace android {
 namespace uirenderer {
 
-Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) {
-    Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap);
-    if (!texture) {
-        return caches.textureCache.get(bitmap);
-    }
-    return texture;
+////////////////////////////////////////////////////////////////////////////////
+// OffscreenBuffer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+        uint32_t viewportWidth, uint32_t viewportHeight)
+        : texture(caches)
+        , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) {
+    texture.width = textureWidth;
+    texture.height = textureHeight;
+
+    caches.textureState().activateTexture(0);
+    glGenTextures(1, &texture.id);
+    caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
+
+    texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+    // not setting filter on texture, since it's set when drawing, based on transform
+
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
 }
 
-void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) {
-    bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
-    renderState.scissor().setEnabled(useScissor);
-    if (useScissor) {
-        const Rect& clip = state.computedState.clipRect;
-        renderState.scissor().set(clip.left, viewportHeight - clip.bottom,
-            clip.getWidth(), clip.getHeight());
-    }
-    renderState.render(glop, orthoMatrix);
-    didDraw = true;
+////////////////////////////////////////////////////////////////////////////////
+// BakedOpRenderer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
+    LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
+    // TODO: really should be caching these!
+    OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height);
+    mRenderTarget.offscreenBuffer = buffer;
+
+    // create and bind framebuffer
+    mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
+    mRenderState.bindFramebuffer(mRenderTarget.frameBufferId);
+
+    // attach the texture to the FBO
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+            buffer->texture.id, 0);
+    LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
+    LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
+            "framebuffer incomplete!");
+
+    // Clear the FBO
+    mRenderState.scissor().setEnabled(false);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    // Change the viewport & ortho projection
+    setViewport(width, height);
+    return buffer;
 }
 
-void BakedOpRenderer::startFrame(Info& info) {
-    info.renderState.setViewport(info.viewportWidth, info.viewportHeight);
-    info.renderState.blend().syncEnabled();
-    Caches::getInstance().clearGarbage();
+void BakedOpRenderer::endLayer() {
+    mRenderTarget.offscreenBuffer = nullptr;
 
-    if (!info.opaque) {
+    // Detach the texture from the FBO
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+    LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
+    mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId);
+    mRenderTarget.frameBufferId = -1;
+}
+
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
+    mRenderState.bindFramebuffer(0);
+    setViewport(width, height);
+    mCaches.clearGarbage();
+
+    if (!mOpaque) {
         // TODO: partial invalidate!
-        info.renderState.scissor().setEnabled(false);
+        mRenderState.scissor().setEnabled(false);
         glClear(GL_COLOR_BUFFER_BIT);
-        info.didDraw = true;
+        mHasDrawn = true;
     }
 }
-void BakedOpRenderer::endFrame(Info& info) {
-    info.caches.pathCache.trim();
-    info.caches.tessellationCache.trim();
+
+void BakedOpRenderer::endFrame() {
+    mCaches.pathCache.trim();
+    mCaches.tessellationCache.trim();
 
 #if DEBUG_OPENGL
     GLUtils::dumpGLErrors();
 #endif
 
 #if DEBUG_MEMORY_USAGE
-    info.caches.dumpMemoryUsage();
+    mCaches.dumpMemoryUsage();
 #else
     if (Properties::debugLevel & kDebugMemory) {
-        info.caches.dumpMemoryUsage();
+        mCaches.dumpMemoryUsage();
     }
 #endif
 }
 
-void BakedOpRenderer::onRenderNodeOp(Info&, const RenderNodeOp&, const BakedOpState&) {
+void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) {
+    mRenderTarget.viewportWidth = width;
+    mRenderTarget.viewportHeight = height;
+    mRenderTarget.orthoMatrix.loadOrtho(width, height);
+
+    mRenderState.setViewport(width, height);
+    mRenderState.blend().syncEnabled();
+}
+
+Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
+    Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
+    if (!texture) {
+        return mCaches.textureCache.get(bitmap);
+    }
+    return texture;
+}
+
+void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) {
+    bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
+    mRenderState.scissor().setEnabled(useScissor);
+    if (useScissor) {
+        const Rect& clip = state.computedState.clipRect;
+        mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom,
+            clip.getWidth(), clip.getHeight());
+    }
+    mRenderState.render(glop, mRenderTarget.orthoMatrix);
+    mHasDrawn = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// static BakedOpDispatcher methods
+////////////////////////////////////////////////////////////////////////////////
+
+void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
     LOG_ALWAYS_FATAL("unsupported operation");
 }
 
-void BakedOpRenderer::onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) {
-    info.caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
-    Texture* texture = info.getTexture(op.bitmap);
+void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
+    renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
+    Texture* texture = renderer.getTexture(op.bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
     const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
             ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
     Glop glop;
-    GlopBuilder(info.renderState, info.caches, &glop)
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshTexturedUnitQuad(texture->uvMapper)
             .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
             .build();
-    info.renderGlop(state, glop);
+    renderer.renderGlop(state, glop);
 }
 
-void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
     Glop glop;
-    GlopBuilder(info.renderState, info.caches, &glop)
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshUnitQuad()
             .setFillPaint(*op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewMapUnitToRect(op.unmappedBounds)
             .build();
-    info.renderGlop(state, glop);
+    renderer.renderGlop(state, glop);
 }
 
-void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
     Glop glop;
-    GlopBuilder(info.renderState, info.caches, &glop)
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
             .setFillPaint(*op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewOffsetRect(0, 0, op.unmappedBounds)
             .build();
-    info.renderGlop(state, glop);
+    renderer.renderGlop(state, glop);
 }
 
-void BakedOpRenderer::onBeginLayerOp(Info& info, const BeginLayerOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
     LOG_ALWAYS_FATAL("unsupported operation");
 }
 
-void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
     LOG_ALWAYS_FATAL("unsupported operation");
 }
 
-void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) {
-    LOG_ALWAYS_FATAL("unsupported operation");
+void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
+    OffscreenBuffer* buffer = *op.layerHandle;
+
+    // TODO: extend this to handle HW layers & paint properties which
+    // reside in node.properties().layerProperties()
+    float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha;
+    const bool tryToSnap = state.computedState.transform.isPureTranslate();
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshTexturedUvQuad(nullptr, buffer->texCoords)
+            .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+            .build();
+    renderer.renderGlop(state, glop);
+
+    // destroy and delete, since each clipped saveLayer is only drawn once.
+    buffer->texture.deleteTexture();
+
+    // TODO: return texture/offscreenbuffer to cache!
+    delete buffer;
 }
 
 } // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index f45dbe4..16afad4 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -25,48 +25,85 @@
 
 class Caches;
 struct Glop;
+class Layer;
 class RenderState;
 
+/**
+ * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
+ * encompasses enough information to draw it back on screen (minus paint properties, which are held
+ * by LayerOp).
+ */
+class OffscreenBuffer {
+public:
+    OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+            uint32_t viewportWidth, uint32_t viewportHeight);
+
+    Texture texture;
+    Rect texCoords;
+    Region region;
+};
+
+/**
+ * Main rendering manager for a collection of work - one frame + any contained FBOs.
+ *
+ * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
+ * place where FBOs are bound, created, and destroyed.
+ *
+ * All rendering operations will be sent by the Dispatcher, a collection of static methods,
+ * which has intentionally limited access to the renderer functionality.
+ */
 class BakedOpRenderer {
 public:
-    class Info {
-    public:
-        Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque)
-                : renderState(renderState)
-                , caches(caches)
-                , opaque(opaque)
-                , viewportWidth(viewportWidth)
-                , viewportHeight(viewportHeight) {
-            orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
-        }
+    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
+            : mRenderState(renderState)
+            , mCaches(caches)
+            , mOpaque(opaque) {
+    }
 
-        Texture* getTexture(const SkBitmap* bitmap);
+    RenderState& renderState() { return mRenderState; }
+    Caches& caches() { return mCaches; }
 
-        void renderGlop(const BakedOpState& state, const Glop& glop);
-        RenderState& renderState;
-        Caches& caches;
+    void startFrame(uint32_t width, uint32_t height);
+    void endFrame();
+    OffscreenBuffer* startLayer(uint32_t width, uint32_t height);
+    void endLayer();
 
-        bool didDraw = false;
-        bool opaque;
+    Texture* getTexture(const SkBitmap* bitmap);
 
+    void renderGlop(const BakedOpState& state, const Glop& glop);
+    bool didDraw() { return mHasDrawn; }
+private:
+    void setViewport(uint32_t width, uint32_t height);
 
-        // where should these live? layer state object?
-        int viewportWidth;
-        int viewportHeight;
+    RenderState& mRenderState;
+    Caches& mCaches;
+    bool mOpaque;
+    bool mHasDrawn = false;
+
+    // render target state - setup by start/end layer/frame
+    // only valid to use in between start/end pairs.
+    struct {
+        GLuint frameBufferId = 0;
+        OffscreenBuffer* offscreenBuffer = nullptr;
+        uint32_t viewportWidth = 0;
+        uint32_t viewportHeight = 0;
         Matrix4 orthoMatrix;
-    };
+    } mRenderTarget;
+};
 
-    static void startFrame(Info& info);
-    static void endFrame(Info& info);
-
-    /**
-     * Declare all "onBitmapOp(...)" style function for every op type.
-     *
-     * These functions will perform the actual rendering of the individual operations in OpenGL,
-     * given the transform/clip and other state built into the BakedOpState object passed in.
-     */
-    #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info& info, const Type& op, const BakedOpState& state);
-    MAP_OPS(BAKED_OP_RENDERER_METHOD);
+/**
+ * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
+ * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
+ *
+ * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
+ * minimal through public BakedOpRenderer APIs.
+ */
+class BakedOpDispatcher {
+public:
+    // Declares all "onBitmapOp(...)" style methods for every op type
+#define DISPATCH_METHOD(Type) \
+        static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
+    MAP_OPS(DISPATCH_METHOD);
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index bad3972..f5e5735 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -584,7 +584,7 @@
     // it to the bitmap pile
     SkBitmap bitmap;
     SkShader::TileMode xy[2];
-    if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
+    if (shader->isABitmap(&bitmap, nullptr, xy)) {
         refBitmap(bitmap);
         return;
     }
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index f99d92b..489ebc1 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -155,8 +155,7 @@
 
     if (fbo) {
         if (flush) LayerRenderer::flushLayer(renderState, this);
-        // If put fails the cache will delete the FBO
-        caches.fboCache.put(fbo);
+        renderState.deleteFramebuffer(fbo);
         fbo = 0;
     }
 }
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 227271d..e9e5d81 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -189,7 +189,7 @@
     LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
 
     Caches& caches = Caches::getInstance();
-    GLuint fbo = caches.fboCache.get();
+    GLuint fbo = renderState.genFramebuffer();
     if (!fbo) {
         ALOGW("Could not obtain an FBO");
         return nullptr;
@@ -204,7 +204,7 @@
 
     // We first obtain a layer before comparing against the max texture size
     // because layers are not allocated at the exact desired size. They are
-    // always created slighly larger to improve recycling
+    // always created slightly larger to improve recycling
     const uint32_t maxTextureSize = caches.maxTextureSize;
     if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) {
         ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)",
@@ -357,7 +357,7 @@
             && bitmap->width() <= caches.maxTextureSize
             && bitmap->height() <= caches.maxTextureSize) {
 
-        GLuint fbo = caches.fboCache.get();
+        GLuint fbo = renderState.getFramebuffer();
         if (!fbo) {
             ALOGW("Could not obtain an FBO");
             return false;
@@ -465,7 +465,7 @@
         layer->setAlpha(alpha, mode);
         layer->setFbo(previousLayerFbo);
         caches.textureState().deleteTexture(texture);
-        caches.fboCache.put(fbo);
+        renderState.deleteFramebuffer(fbo);
         renderState.setViewport(previousViewportWidth, previousViewportHeight);
 
         return status;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index c1417c4..ddeb336 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -203,7 +203,7 @@
 };
 
 // iterate back toward target to see if anything drawn since should overlap the new op
-// if no target, merging ops still interate to find similar batch to insert after
+// if no target, merging ops still iterate to find similar batch to insert after
 void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
         BatchBase** targetBatch, size_t* insertBatchIndex) const {
     for (int i = mBatches.size() - 1; i >= 0; i--) {
@@ -277,7 +277,8 @@
     }
 }
 
-void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const {
+void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const {
+    ATRACE_NAME("flush drawing commands");
     for (const BatchBase* batch : mBatches) {
         // TODO: different behavior based on batch->isMerging()
         for (const BakedOpState* op : batch->getOps()) {
@@ -292,18 +293,14 @@
     }
 }
 
-OpReorderer::OpReorderer()
+OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+        const std::vector< sp<RenderNode> >& nodes)
         : mCanvasState(*this) {
-    mLayerReorderers.emplace_back();
+    ATRACE_NAME("prepare drawing commands");
+
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
     mLayerStack.push_back(0);
-}
 
-void OpReorderer::onViewportInitialized() {}
-
-void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
-
-void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight,
-        const std::vector< sp<RenderNode> >& nodes) {
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
             Vector3());
@@ -321,13 +318,22 @@
     }
 }
 
-void OpReorderer::defer(int viewportWidth, int viewportHeight, const DisplayList& displayList) {
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+        : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
+
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+    mLayerStack.push_back(0);
+
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             0, 0, viewportWidth, viewportHeight, Vector3());
     deferImpl(displayList);
 }
 
+void OpReorderer::onViewportInitialized() {}
+
+void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
 /**
  * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
  *
@@ -348,15 +354,6 @@
     }
 }
 
-void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
-    ATRACE_NAME("flush drawing commands");
-    // Relay through layers in reverse order, since layers
-    // later in the list will be drawn by earlier ones
-    for (int i = mLayerReorderers.size() - 1; i >= 0; i--) {
-        mLayerReorderers[i].replayBakedOpsImpl(arg, receivers);
-    }
-}
-
 void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
     if (op.renderNode->nothingToDraw()) {
         return;
@@ -405,15 +402,17 @@
 
 // TODO: test rejection at defer time, where the bounds become empty
 void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+    const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+    const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+
     mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
     mCanvasState.writableSnapshot()->transform->loadIdentity();
-    mCanvasState.writableSnapshot()->initializeViewport(
-            (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight());
+    mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
     mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
 
     // create a new layer, and push its index on the stack
     mLayerStack.push_back(mLayerReorderers.size());
-    mLayerReorderers.emplace_back();
+    mLayerReorderers.emplace_back(layerWidth, layerHeight);
     mLayerReorderers.back().beginLayerOp = &op;
 }
 
@@ -432,7 +431,8 @@
             beginLayerOp.unmappedBounds,
             beginLayerOp.localMatrix,
             beginLayerOp.localClipRect,
-            beginLayerOp.paint);
+            beginLayerOp.paint,
+            &mLayerReorderers[finishedLayerIndex].offscreenBuffer);
     BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
 
     if (bakedOpState) {
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 73dc9af..927ecfa 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -33,6 +33,7 @@
 class BakedOpState;
 class BatchBase;
 class MergingOpBatch;
+class OffscreenBuffer;
 class OpBatch;
 class Rect;
 
@@ -55,7 +56,7 @@
 }
 
 class OpReorderer : public CanvasStateClient {
-    typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
+    typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpDispatcher;
 
     /**
      * Stores the deferred render operations and state used to compute ordering
@@ -63,6 +64,10 @@
      */
     class LayerReorderer {
     public:
+        LayerReorderer(uint32_t width, uint32_t height)
+                : width(width)
+                , height(height) {}
+
         // iterate back toward target to see if anything drawn since should overlap the new op
         // if no target, merging ops still iterate to find similar batch to insert after
         void locateInsertIndex(int batchId, const Rect& clippedBounds,
@@ -75,7 +80,11 @@
         void deferMergeableOp(LinearAllocator& allocator,
                 BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
 
-        void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const;
+        void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const;
+
+        bool empty() const {
+            return mBatches.empty();
+        }
 
         void clear() {
             mBatches.clear();
@@ -83,9 +92,12 @@
 
         void dump() const;
 
+        OffscreenBuffer* offscreenBuffer = nullptr;
         const BeginLayerOp* beginLayerOp = nullptr;
-
+        const uint32_t width;
+        const uint32_t height;
     private:
+
         std::vector<BatchBase*> mBatches;
 
         /**
@@ -100,14 +112,13 @@
 
     };
 public:
-    OpReorderer();
-    virtual ~OpReorderer() {}
-
     // TODO: not final, just presented this way for simplicity. Layers too?
-    void defer(const SkRect& clip, int viewportWidth, int viewportHeight,
+    OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
             const std::vector< sp<RenderNode> >& nodes);
 
-    void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+
+    virtual ~OpReorderer() {}
 
     /**
      * replayBakedOps() is templated based on what class will receive ops being replayed.
@@ -117,20 +128,33 @@
      *
      * For example a BitmapOp would resolve, via the lambda lookup, to calling:
      *
-     * StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state);
+     * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state);
      */
 #define BAKED_OP_RECEIVER(Type) \
-    [](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
-        StaticReceiver::on##Type(*(static_cast<Arg*>(internalArg)), static_cast<const Type&>(op), state); \
+    [](void* internalRenderer, const RecordedOp& op, const BakedOpState& state) { \
+        StaticDispatcher::on##Type(*(static_cast<Renderer*>(internalRenderer)), static_cast<const Type&>(op), state); \
     },
-    template <typename StaticReceiver, typename Arg>
-    void replayBakedOps(Arg& arg) {
-        static BakedOpReceiver receivers[] = {
+    template <typename StaticDispatcher, typename Renderer>
+    void replayBakedOps(Renderer& renderer) {
+        static BakedOpDispatcher receivers[] = {
             MAP_OPS(BAKED_OP_RECEIVER)
         };
-        StaticReceiver::startFrame(arg);
-        replayBakedOpsImpl((void*)&arg, receivers);
-        StaticReceiver::endFrame(arg);
+
+        // Relay through layers in reverse order, since layers
+        // later in the list will be drawn by earlier ones
+        for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
+            LayerReorderer& layer = mLayerReorderers[i];
+            if (!layer.empty()) {
+                layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height);
+                layer.replayBakedOpsImpl((void*)&renderer, receivers);
+                renderer.endLayer();
+            }
+        }
+
+        const LayerReorderer& fbo0 = mLayerReorderers[0];
+        renderer.startFrame(fbo0.width, fbo0.height);
+        fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
+        renderer.endFrame();
     }
 
     void dump() const {
@@ -155,7 +179,7 @@
 
     void deferImpl(const DisplayList& displayList);
 
-    void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
+    void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
 
     /**
      * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index d4f65b6..8c3603b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -673,7 +673,7 @@
 
 bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
     layer->clipRect.set(clip);
-    layer->setFbo(mCaches.fboCache.get());
+    layer->setFbo(mRenderState.genFramebuffer());
 
     writableSnapshot()->region = &writableSnapshot()->layer->region;
     writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index dd01637..7874d85 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -29,6 +29,7 @@
 namespace android {
 namespace uirenderer {
 
+class OffscreenBuffer;
 class RenderNode;
 struct Vertex;
 
@@ -136,8 +137,12 @@
 };
 
 struct LayerOp : RecordedOp {
-    LayerOp(BASE_PARAMS)
-            : SUPER(LayerOp) {}
+    LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
+            : SUPER(LayerOp)
+            , layerHandle(layerHandle) {}
+    // Records a handle to the Layer object, since the Layer itself won't be
+    // constructed until after this operation is constructed.
+    OffscreenBuffer** layerHandle;
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 1f113bc..273af3a 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -458,7 +458,7 @@
     // it to the bitmap pile
     SkBitmap bitmap;
     SkShader::TileMode xy[2];
-    if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
+    if (shader->isABitmap(&bitmap, nullptr, xy)) {
         refBitmap(bitmap);
         return;
     }
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 9c32b1a..454ee24 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -27,6 +27,8 @@
 #include "Snapshot.h"
 
 #include "SkDrawFilter.h"
+#include "SkPaint.h"
+#include "SkTLazy.h"
 #include <vector>
 
 namespace android {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 351fbaa..39cb8e9 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -16,13 +16,6 @@
 
 #include "RenderNode.h"
 
-#include <algorithm>
-#include <string>
-
-#include <SkCanvas.h>
-#include <algorithm>
-
-
 #include "DamageAccumulator.h"
 #include "Debug.h"
 #if HWUI_NEW_OPS
@@ -39,6 +32,12 @@
 #include "protos/hwui.pb.h"
 #include "protos/ProtoHelpers.h"
 
+#include <SkCanvas.h>
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
 namespace android {
 namespace uirenderer {
 
@@ -79,6 +78,11 @@
     mNeedsDisplayListSync = true;
     delete mStagingDisplayList;
     mStagingDisplayList = displayList;
+    // If mParentCount == 0 we are the sole reference to this RenderNode,
+    // so immediately free the old display list
+    if (!mParentCount && !mStagingDisplayList) {
+        deleteDisplayList();
+    }
 }
 
 /**
@@ -250,7 +254,8 @@
 
     bool transformUpdateNeeded = false;
     if (!mLayer) {
-        mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight());
+        mLayer = LayerRenderer::createRenderLayer(
+                info.canvasContext.getRenderState(), getWidth(), getHeight());
         applyLayerPropertiesToLayer(info);
         damageSelf(info);
         transformUpdateNeeded = true;
@@ -269,9 +274,16 @@
     if (!mLayer) {
         Caches::getInstance().dumpMemoryUsage();
         if (info.errorHandler) {
-            std::string msg = "Unable to create layer for ";
-            msg += getName();
-            info.errorHandler->onError(msg);
+            std::ostringstream err;
+            err << "Unable to create layer for " << getName();
+            const int maxTextureSize = Caches::getInstance().maxTextureSize;
+            if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
+                err << ", size " << getWidth() << "x" << getHeight()
+                        << " exceeds max size " << maxTextureSize;
+            } else {
+                err << ", see logcat for more info";
+            }
+            info.errorHandler->onError(err.str());
         }
         return;
     }
@@ -293,12 +305,10 @@
         info.renderer->pushLayerUpdate(mLayer);
     }
 
-    if (info.canvasContext) {
-        // There might be prefetched layers that need to be accounted for.
-        // That might be us, so tell CanvasContext that this layer is in the
-        // tree and should not be destroyed.
-        info.canvasContext->markLayerInUse(this);
-    }
+    // There might be prefetched layers that need to be accounted for.
+    // That might be us, so tell CanvasContext that this layer is in the
+    // tree and should not be destroyed.
+    info.canvasContext.markLayerInUse(this);
 }
 
 /**
@@ -419,7 +429,8 @@
         TextureCache& cache = Caches::getInstance().textureCache;
         info.out.hasFunctors |= subtree->getFunctors().size();
         for (auto&& bitmapResource : subtree->getBitmapResources()) {
-            info.prepareTextures = cache.prefetchAndMarkInUse(info.canvasContext, bitmapResource);
+            void* ownerToken = &info.canvasContext;
+            info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource);
         }
         for (auto&& op : subtree->getChildren()) {
             RenderNode* childNode = op->renderNode;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 36633b5..a8f8134 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -194,15 +194,13 @@
 
 void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
     SkCanvas* newCanvas = new SkCanvas(bitmap);
-    SkASSERT(newCanvas);
 
     if (!bitmap.isNull()) {
         // Copy the canvas matrix & clip state.
         newCanvas->setMatrix(mCanvas->getTotalMatrix());
-        if (NULL != mCanvas->getDevice() && NULL != newCanvas->getDevice()) {
-            ClipCopier copier(newCanvas);
-            mCanvas->replayClips(&copier);
-        }
+
+        ClipCopier copier(newCanvas);
+        mCanvas->replayClips(&copier);
     }
 
     // unrefs the existing canvas
@@ -217,15 +215,15 @@
 // ----------------------------------------------------------------------------
 
 bool SkiaCanvas::isOpaque() {
-    return mCanvas->getDevice()->accessBitmap(false).isOpaque();
+    return mCanvas->imageInfo().isOpaque();
 }
 
 int SkiaCanvas::width() {
-    return mCanvas->getBaseLayerSize().width();
+    return mCanvas->imageInfo().width();
 }
 
 int SkiaCanvas::height() {
-    return mCanvas->getBaseLayerSize().height();
+    return mCanvas->imageInfo().height();
 }
 
 // ----------------------------------------------------------------------------
@@ -581,7 +579,7 @@
                             float dstRight, float dstBottom, const SkPaint* paint) {
     SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
     SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
-    mCanvas->drawBitmapRectToRect(bitmap, &srcRect, dstRect, paint);
+    mCanvas->drawBitmapRect(bitmap, srcRect, dstRect, paint);
 }
 
 void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index c3f5eb2..2d5f70f 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -125,7 +125,7 @@
 }
 
 void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr,
-        const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags) {
+        const SkRect& dst, const SkPaint* paint, SrcRectConstraint) {
     SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height());
     // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src?
     mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index 0de9650..2fe4327 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -63,7 +63,7 @@
     virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top,
                               const SkPaint*) override;
     virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst,
-                                  const SkPaint* paint, DrawBitmapRectFlags flags) override;
+                                  const SkPaint* paint, SrcRectConstraint) override;
     virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
                                   const SkRect& dst, const SkPaint*) override;
     virtual void onDrawSprite(const SkBitmap&, int left, int top,
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 6c105cf..83652c6 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -204,7 +204,7 @@
         SkiaShaderData::BitmapShaderData* outData) {
     SkBitmap bitmap;
     SkShader::TileMode xy[2];
-    if (shader.asABitmap(&bitmap, nullptr, xy) != SkShader::kDefault_BitmapType) {
+    if (!shader.isABitmap(&bitmap, nullptr, xy)) {
         return false;
     }
 
@@ -272,7 +272,7 @@
     }
 
     // The shader is not a gradient. Check for a bitmap shader.
-    if (shader.asABitmap(nullptr, nullptr, nullptr) == SkShader::kDefault_BitmapType) {
+    if (shader.isABitmap()) {
         return kBitmap_SkiaShaderType;
     }
     return kNone_SkiaShaderType;
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 98e6146..1c31487 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -55,70 +55,46 @@
         MODE_RT_ONLY,
     };
 
-    explicit TreeInfo(TraversalMode mode, RenderState& renderState)
-        : mode(mode)
-        , prepareTextures(mode == MODE_FULL)
-        , runAnimations(true)
-        , damageAccumulator(nullptr)
-        , renderState(renderState)
-        , renderer(nullptr)
-        , errorHandler(nullptr)
-        , canvasContext(nullptr)
-    {}
-
-    explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
-        : mode(mode)
-        , prepareTextures(mode == MODE_FULL)
-        , runAnimations(clone.runAnimations)
-        , damageAccumulator(clone.damageAccumulator)
-        , renderState(clone.renderState)
-        , renderer(clone.renderer)
-        , errorHandler(clone.errorHandler)
-        , canvasContext(clone.canvasContext)
+    TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext)
+            : mode(mode)
+            , prepareTextures(mode == MODE_FULL)
+            , canvasContext(canvasContext)
     {}
 
     TraversalMode mode;
     // TODO: Remove this? Currently this is used to signal to stop preparing
     // textures if we run out of cache space.
     bool prepareTextures;
+    renderthread::CanvasContext& canvasContext;
     // TODO: buildLayer uses this to suppress running any animations, but this
     // should probably be refactored somehow. The reason this is done is
     // because buildLayer is not setup for injecting the animationHook, as well
     // as this being otherwise wasted work as all the animators will be
     // re-evaluated when the frame is actually drawn
-    bool runAnimations;
+    bool runAnimations = true;
 
     // Must not be null during actual usage
-    DamageAccumulator* damageAccumulator;
-    RenderState& renderState;
+    DamageAccumulator* damageAccumulator = nullptr;
     // The renderer that will be drawing the next frame. Use this to push any
     // layer updates or similar. May be NULL.
-    OpenGLRenderer* renderer;
-    ErrorHandler* errorHandler;
-    // May be NULL (TODO: can it really?)
-    renderthread::CanvasContext* canvasContext;
+    OpenGLRenderer* renderer = nullptr;
+    ErrorHandler* errorHandler = nullptr;
 
     struct Out {
-        Out()
-            : hasFunctors(false)
-            , hasAnimations(false)
-            , requiresUiRedraw(false)
-            , canDrawThisFrame(true)
-        {}
-        bool hasFunctors;
+        bool hasFunctors = false;
         // This is only updated if evaluateAnimations is true
-        bool hasAnimations;
+        bool hasAnimations = false;
         // This is set to true if there is an animation that RenderThread cannot
         // animate itself, such as if hasFunctors is true
         // This is only set if hasAnimations is true
-        bool requiresUiRedraw;
+        bool requiresUiRedraw = false;
         // This is set to true if draw() can be called this frame
         // false means that we must delay until the next vsync pulse as frame
         // production is outrunning consumption
         // NOTE that if this is false CanvasContext will set either requiresUiRedraw
         // *OR* will post itself for the next vsync automatically, use this
         // only to avoid calling draw()
-        bool canDrawThisFrame;
+        bool canDrawThisFrame = true;
     } out;
 
     // TODO: Damage calculations
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index cf96d44..43f170f 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,8 +48,7 @@
 void BM_OpReorderer_defer::Run(int iters) {
     StartBenchmarkTiming();
     for (int i = 0; i < iters; i++) {
-        OpReorderer reorderer;
-        reorderer.defer(200, 200, *sReorderingDisplayList);
+        OpReorderer reorderer(200, 200, *sReorderingDisplayList);
         MicroBench::DoNotOptimize(&reorderer);
     }
     StopBenchmarkTiming();
@@ -60,12 +59,11 @@
     TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
         StartBenchmarkTiming();
         for (int i = 0; i < iters; i++) {
-            OpReorderer reorderer;
-            reorderer.defer(200, 200, *sReorderingDisplayList);
-            MicroBench::DoNotOptimize(&reorderer);
+            OpReorderer reorderer(200, 200, *sReorderingDisplayList);
 
-            BakedOpRenderer::Info info(caches, renderState, 200, 200, true);
-            reorderer.replayBakedOps<BakedOpRenderer>(info);
+            BakedOpRenderer renderer(caches, renderState, true);
+            reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
+            MicroBench::DoNotOptimize(&renderer);
         }
         StopBenchmarkTiming();
     });
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index dfa70ac..9637117 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -125,6 +125,21 @@
     }
 }
 
+GLuint RenderState::genFramebuffer() {
+    GLuint ret;
+    glGenFramebuffers(1, &ret);
+    return ret;
+}
+
+void RenderState::deleteFramebuffer(GLuint fbo) {
+    if (mFramebuffer == fbo) {
+        // GL defines that deleting the currently bound FBO rebinds FBO 0.
+        // Reflect this in our cached value.
+        mFramebuffer = 0;
+    }
+    glDeleteFramebuffers(1, &fbo);
+}
+
 void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
     if (mode == DrawGlInfo::kModeProcessNoContext) {
         // If there's no context we don't need to interrupt as there's
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 9ae0845..3cda170 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -16,24 +16,25 @@
 #ifndef RENDERSTATE_H
 #define RENDERSTATE_H
 
-#include <set>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <utils/Mutex.h>
-#include <utils/Functor.h>
-#include <utils/RefBase.h>
-#include <private/hwui/DrawGlInfo.h>
-#include <renderstate/Blend.h>
-
 #include "AssetAtlas.h"
 #include "Caches.h"
 #include "Glop.h"
+#include "renderstate/Blend.h"
 #include "renderstate/MeshState.h"
 #include "renderstate/PixelBufferState.h"
 #include "renderstate/Scissor.h"
 #include "renderstate/Stencil.h"
 #include "utils/Macros.h"
 
+#include <set>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <ui/Region.h>
+#include <utils/Mutex.h>
+#include <utils/Functor.h>
+#include <utils/RefBase.h>
+#include <private/hwui/DrawGlInfo.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -49,6 +50,8 @@
 // wrapper of Caches for users to migrate to.
 class RenderState {
     PREVENT_COPY_AND_ASSIGN(RenderState);
+    friend class renderthread::RenderThread;
+    friend class Caches;
 public:
     void onGLContextCreated();
     void onGLContextDestroyed();
@@ -57,7 +60,9 @@
     void getViewport(GLsizei* outWidth, GLsizei* outHeight);
 
     void bindFramebuffer(GLuint fbo);
-    GLint getFramebuffer() { return mFramebuffer; }
+    GLuint getFramebuffer() { return mFramebuffer; }
+    GLuint genFramebuffer();
+    void deleteFramebuffer(GLuint fbo);
 
     void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
 
@@ -93,10 +98,8 @@
     Stencil& stencil() { return *mStencil; }
 
     void dump();
-private:
-    friend class renderthread::RenderThread;
-    friend class Caches;
 
+private:
     void interruptForFunctorInvoke();
     void resumeFromFunctorInvoke();
     void assertOnGLThread();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f571426..fac26dc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -27,6 +27,7 @@
 #include "renderstate/RenderState.h"
 #include "renderstate/Stencil.h"
 #include "protos/hwui.pb.h"
+#include "utils/TimeUtils.h"
 
 #if HWUI_NEW_OPS
 #include "BakedOpRenderer.h"
@@ -108,6 +109,7 @@
         const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer);
         mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
         mHaveNewSurface = true;
+        mSwapHistory.clear();
         makeCurrent();
     } else {
         mRenderThread.removeFrameCallback(this);
@@ -197,7 +199,6 @@
 
     info.damageAccumulator = &mDamageAccumulator;
     info.renderer = mCanvas;
-    info.canvasContext = this;
 
     mAnimationContext->startFrame(info.mode);
     for (const sp<RenderNode>& node : mRenderNodes) {
@@ -217,13 +218,30 @@
         return;
     }
 
-    int runningBehind = 0;
-    // TODO: This query is moderately expensive, investigate adding some sort
-    // of fast-path based off when we last called eglSwapBuffers() as well as
-    // last vsync time. Or something.
-    mNativeWindow->query(mNativeWindow.get(),
-            NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
-    info.out.canDrawThisFrame = !runningBehind;
+    if (CC_LIKELY(mSwapHistory.size())) {
+        nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
+        const SwapHistory& lastSwap = mSwapHistory.back();
+        int vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
+        // The slight fudge-factor is to deal with cases where
+        // the vsync was estimated due to being slow handling the signal.
+        // See the logic in TimeLord#computeFrameTimeNanos or in
+        // Choreographer.java for details on when this happens
+        if (vsyncDelta < 2_ms) {
+            // Already drew for this vsync pulse, UI draw request missed
+            // the deadline for RT animations
+            info.out.canDrawThisFrame = false;
+        } else if (lastSwap.swapTime < latestVsync) {
+            info.out.canDrawThisFrame = true;
+        } else {
+            // We're maybe behind? Find out for sure
+            int runningBehind = 0;
+            mNativeWindow->query(mNativeWindow.get(),
+                    NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
+            info.out.canDrawThisFrame = !runningBehind;
+        }
+    } else {
+        info.out.canDrawThisFrame = true;
+    }
 
     if (!info.out.canDrawThisFrame) {
         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
@@ -266,11 +284,11 @@
 
     Frame frame = mEglManager.beginFrame(mEglSurface);
 
-    if (frame.width() != lastFrameWidth || frame.height() != lastFrameHeight) {
+    if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
         // can't rely on prior content of window if viewport size changes
         dirty.setEmpty();
-        lastFrameWidth = frame.width();
-        lastFrameHeight = frame.height();
+        mLastFrameWidth = frame.width();
+        mLastFrameHeight = frame.height();
     } else if (mHaveNewSurface || frame.bufferAge() == 0) {
         // New surface needs a full draw
         dirty.setEmpty();
@@ -297,7 +315,7 @@
     // last frame so there's nothing to union() against
     // Therefore we only care about the > 1 case.
     if (frame.bufferAge() > 1) {
-        if (frame.bufferAge() > (int) mDamageHistory.size()) {
+        if (frame.bufferAge() > (int) mSwapHistory.size()) {
             // We don't have enough history to handle this old of a buffer
             // Just do a full-draw
             dirty.set(0, 0, frame.width(), frame.height());
@@ -305,27 +323,22 @@
             // At this point we haven't yet added the latest frame
             // to the damage history (happens below)
             // So we need to damage
-            for (int i = mDamageHistory.size() - 1;
-                    i > ((int) mDamageHistory.size()) - frame.bufferAge(); i--) {
-                dirty.join(mDamageHistory[i]);
+            for (int i = mSwapHistory.size() - 1;
+                    i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) {
+                dirty.join(mSwapHistory[i].damage);
             }
         }
     }
 
-    // Add the screen damage to the ring buffer.
-    mDamageHistory.next() = screenDirty;
-
     mEglManager.damageFrame(frame, dirty);
 
 #if HWUI_NEW_OPS
-    OpReorderer reorderer;
-    reorderer.defer(dirty, frame.width(), frame.height(), mRenderNodes);
-    BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
-            frame.width(), frame.height(), mOpaque);
+    OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
+    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
     // TODO: profiler().draw(mCanvas);
-    reorderer.replayBakedOps<BakedOpRenderer>(info);
+    reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
 
-    bool drew = info.didDraw;
+    bool drew = renderer.didDraw();
 
 #else
     mCanvas->prepareDirty(frame.width(), frame.height(),
@@ -447,6 +460,10 @@
         if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
             setSurface(nullptr);
         }
+        SwapHistory& swap = mSwapHistory.next();
+        swap.damage = screenDirty;
+        swap.swapTime = systemTime(CLOCK_MONOTONIC);
+        swap.vsyncTime = mRenderThread.timeLord().latestVsync();
         mHaveNewSurface = false;
     }
 
@@ -489,7 +506,7 @@
         .setVsync(mRenderThread.timeLord().computeFrameTimeNanos(),
                 mRenderThread.timeLord().latestVsync());
 
-    TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
     prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node);
     if (info.out.canDrawThisFrame) {
         draw();
@@ -533,7 +550,7 @@
     // buildLayer() will leave the tree in an unknown state, so we must stop drawing
     stopDrawing();
 
-    TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
+    TreeInfo info(TreeInfo::MODE_FULL, *this);
     info.damageAccumulator = &mDamageAccumulator;
     info.renderer = mCanvas;
     info.runAnimations = false;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 16956e6..30e6562 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -130,6 +130,10 @@
         mContentDrawBounds.set(left, top, right, bottom);
     }
 
+    RenderState& getRenderState() {
+        return mRenderThread.renderState();
+    }
+
 private:
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
@@ -141,8 +145,8 @@
 
     void freePrefetechedLayers();
 
-    int lastFrameWidth = 0;
-    int lastFrameHeight = 0;
+    EGLint mLastFrameWidth = 0;
+    EGLint mLastFrameHeight = 0;
 
     RenderThread& mRenderThread;
     EglManager& mEglManager;
@@ -150,7 +154,13 @@
     EGLSurface mEglSurface = EGL_NO_SURFACE;
     bool mBufferPreserved = false;
     SwapBehavior mSwapBehavior = kSwap_default;
-    RingBuffer<SkRect, 3> mDamageHistory;
+    struct SwapHistory {
+        SkRect damage;
+        nsecs_t vsyncTime;
+        nsecs_t swapTime;
+    };
+
+    RingBuffer<SwapHistory, 3> mSwapHistory;
 
     bool mOpaque;
     OpenGLRenderer* mCanvas = nullptr;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index a47c9ec..ab860c7 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -87,7 +87,7 @@
     bool canUnblockUiThread;
     bool canDrawThisFrame;
     {
-        TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
+        TreeInfo info(TreeInfo::MODE_FULL, *mContext);
         canUnblockUiThread = syncFrameState(info);
         canDrawThisFrame = info.out.canDrawThisFrame;
     }
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index b353ae6..485759b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -333,7 +333,7 @@
     if (CC_LIKELY(err == EGL_SUCCESS)) {
         return true;
     }
-    if (err == EGL_BAD_SURFACE) {
+    if (err == EGL_BAD_SURFACE || err == EGL_BAD_NATIVE_WINDOW) {
         // For some reason our surface was destroyed out from under us
         // This really shouldn't happen, but if it does we can recover easily
         // by just not trying to use the surface anymore
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 891af91..2eefd37 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -54,15 +54,10 @@
     }
 };
 
-static TestCanvas* startRecording(RenderNode* node) {
-    TestCanvas* renderer = new TestCanvas(
-            node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
-    return renderer;
-}
-
-static void endRecording(TestCanvas* renderer, RenderNode* node) {
-    node->setStagingDisplayList(renderer->finishRecording());
-    delete renderer;
+static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) {
+    TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight());
+    contentCallback(canvas);
+    node.setStagingDisplayList(canvas.finishRecording());
 }
 
 class TreeContentAnimation {
@@ -75,7 +70,7 @@
             frameCount = fc;
         }
     }
-    virtual void createContent(int width, int height, TestCanvas* renderer) = 0;
+    virtual void createContent(int width, int height, TestCanvas* canvas) = 0;
     virtual void doFrame(int frameNr) = 0;
 
     template <class T>
@@ -108,11 +103,9 @@
         proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
         proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
 
-        android::uirenderer::Rect DUMMY;
-
-        TestCanvas* renderer = startRecording(rootNode);
-        animation.createContent(width, height, renderer);
-        endRecording(renderer, rootNode);
+        recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) {
+            animation.createContent(width, height, &canvas); //TODO: no&
+        });
 
         // Do a few cold runs then reset the stats so that the caches are all hot
         for (int i = 0; i < 3; i++) {
@@ -140,19 +133,19 @@
 class ShadowGridAnimation : public TreeContentAnimation {
 public:
     std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
 
         for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
             for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
                 sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
-                renderer->drawRenderNode(card.get());
+                canvas->drawRenderNode(card.get());
                 cards.push_back(card);
             }
         }
 
-        renderer->insertReorderBarrier(false);
+        canvas->insertReorderBarrier(false);
     }
     void doFrame(int frameNr) override {
         int curFrame = frameNr % 150;
@@ -171,9 +164,9 @@
         node->mutateStagingProperties().mutableOutline().setShouldClip(true);
         node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
 
-        TestCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, node.get());
+        recordNode(*node, [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
         return node;
     }
 };
@@ -187,19 +180,19 @@
 class ShadowGrid2Animation : public TreeContentAnimation {
 public:
     std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
 
         for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
             for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
                 sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
-                renderer->drawRenderNode(card.get());
+                canvas->drawRenderNode(card.get());
                 cards.push_back(card);
             }
         }
 
-        renderer->insertReorderBarrier(false);
+        canvas->insertReorderBarrier(false);
     }
     void doFrame(int frameNr) override {
         int curFrame = frameNr % 150;
@@ -218,9 +211,9 @@
         node->mutateStagingProperties().mutableOutline().setShouldClip(true);
         node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
 
-        TestCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, node.get());
+        recordNode(*node, [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
         return node;
     }
 };
@@ -233,15 +226,30 @@
 
 class RectGridAnimation : public TreeContentAnimation {
 public:
-    sp<RenderNode> card;
-    void createContent(int width, int height, TestCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
+    sp<RenderNode> card = new RenderNode();
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
 
-        card = createCard(40, 40, 200, 200);
-        renderer->drawRenderNode(card.get());
+        card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        recordNode(*card, [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
 
-        renderer->insertReorderBarrier(false);
+            SkRegion region;
+            for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+                for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+                }
+            }
+
+            SkPaint paint;
+            paint.setColor(0xff00ffff);
+            canvas.drawRegion(region, paint);
+        });
+        canvas->drawRenderNode(card.get());
+
+        canvas->insertReorderBarrier(false);
     }
     void doFrame(int frameNr) override {
         int curFrame = frameNr % 150;
@@ -249,29 +257,6 @@
         card->mutateStagingProperties().setTranslationY(curFrame);
         card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
     }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        TestCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
-
-        SkRegion region;
-        for (int xOffset = 0; xOffset < width; xOffset+=2) {
-            for (int yOffset = 0; yOffset < height; yOffset+=2) {
-                region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
-            }
-        }
-
-        SkPaint paint;
-        paint.setColor(0xff00ffff);
-        renderer->drawRegion(region, paint);
-
-        endRecording(renderer, node.get());
-        return node;
-    }
 };
 static Benchmark _RectGrid(BenchmarkInfo{
     "rectgrid",
@@ -282,15 +267,22 @@
 
 class OvalAnimation : public TreeContentAnimation {
 public:
-    sp<RenderNode> card;
-    void createContent(int width, int height, TestCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
+    sp<RenderNode> card = new RenderNode();
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
 
-        card = createCard(40, 40, 400, 400);
-        renderer->drawRenderNode(card.get());
+        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        recordNode(*card, [](TestCanvas& canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setColor(0xFF000000);
+            canvas.drawOval(0, 0, 200, 200, paint);
+        });
+        canvas->drawRenderNode(card.get());
 
-        renderer->insertReorderBarrier(false);
+        canvas->insertReorderBarrier(false);
     }
 
     void doFrame(int frameNr) override {
@@ -299,22 +291,6 @@
         card->mutateStagingProperties().setTranslationY(curFrame);
         card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
     }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        TestCanvas* renderer = startRecording(node.get());
-
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setColor(0xFF000000);
-        renderer->drawOval(0, 0, width, height, paint);
-
-        endRecording(renderer, node.get());
-        return node;
-    }
 };
 static Benchmark _Oval(BenchmarkInfo{
     "oval",
@@ -325,7 +301,7 @@
 class PartialDamageTest : public TreeContentAnimation {
 public:
     std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* renderer) override {
+    void createContent(int width, int height, TestCanvas* canvas) override {
         static SkColor COLORS[] = {
                 0xFFF44336,
                 0xFF9C27B0,
@@ -333,13 +309,13 @@
                 0xFF4CAF50,
         };
 
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
 
         for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
             for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
                 sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
                         COLORS[static_cast<int>((y / dp(116))) % 4]);
-                renderer->drawRenderNode(card.get());
+                canvas->drawRenderNode(card.get());
                 cards.push_back(card);
             }
         }
@@ -350,10 +326,10 @@
         cards[0]->mutateStagingProperties().setTranslationY(curFrame);
         cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
 
-        TestCanvas* renderer = startRecording(cards[0].get());
-        renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
-                SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, cards[0].get());
+        recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+            canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
+                    SkXfermode::kSrcOver_Mode);
+        });
     }
 
     static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
@@ -378,9 +354,9 @@
         node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
         node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
 
-        TestCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(color, SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, node.get());
+        recordNode(*node, [color](TestCanvas& canvas) {
+            canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+        });
         return node;
     }
 };
@@ -393,16 +369,24 @@
 });
 
 
-class SimpleRectGridAnimation : public TreeContentAnimation {
+class SaveLayerAnimation : public TreeContentAnimation {
 public:
-    sp<RenderNode> card;
-    void createContent(int width, int height, TestCanvas* renderer) override {
-        SkPaint paint;
-        paint.setColor(0xFF00FFFF);
-        renderer->drawRect(0, 0, width, height, paint);
+    sp<RenderNode> card = new RenderNode();
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
 
-        card = createCard(40, 40, 200, 200);
-        renderer->drawRenderNode(card.get());
+        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        recordNode(*card, [](TestCanvas& canvas) {
+            canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
+            canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
+            canvas.restore();
+            canvas.restore();
+        });
+
+        canvas->drawRenderNode(card.get());
     }
     void doFrame(int frameNr) override {
         int curFrame = frameNr % 150;
@@ -410,24 +394,10 @@
         card->mutateStagingProperties().setTranslationY(curFrame);
         card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
     }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        TestCanvas* renderer = startRecording(node.get());
-        SkPaint paint;
-        paint.setColor(0xFFFF00FF);
-        renderer->drawRect(0, 0, width, height, paint);
-
-        endRecording(renderer, node.get());
-        return node;
-    }
 };
-static Benchmark _SimpleRectGrid(BenchmarkInfo{
-    "simplerectgrid",
-    "A simple collection of rects. "
-    "Low CPU/GPU load.",
-    TreeContentAnimation::run<SimpleRectGridAnimation>
+static Benchmark _SaveLayer(BenchmarkInfo{
+    "savelayer",
+    "A nested pair of clipped saveLayer operations. "
+    "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+    TreeContentAnimation::run<SaveLayerAnimation>
 });
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index 82aebea..bc1b69f 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -85,13 +85,5 @@
     }
 }
 
-#define UNSUPPORTED_OP(Info, Type) \
-        static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
-
-class Info {
-public:
-    int index = 0;
-};
-
 }
 }
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
new file mode 100644
index 0000000..fb760ac5
--- /dev/null
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <utils/FatVector.h>
+
+#include <unit_tests/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+template<class VectorType>
+static bool allocationIsInternal(VectorType& v) {
+    // allocation array (from &v[0] to &v[0] + v.capacity) is
+    // located within the vector object itself
+    return (char*)(&v) <= (char*)(&v[0])
+            && (char*)(&v + 1) >= (char*)(&v[0] + v.capacity());
+}
+
+TEST(FatVector, baseline) {
+    // Verify allocation behavior FatVector contrasts against - allocations are always external
+    std::vector<int> v;
+    for (int i = 0; i < 50; i++) {
+        v.push_back(i);
+        EXPECT_FALSE(allocationIsInternal(v));
+    }
+}
+
+TEST(FatVector, simpleAllocate) {
+    FatVector<int, 4> v;
+    EXPECT_EQ(4u, v.capacity());
+
+    // can insert 4 items into internal buffer
+    for (int i = 0; i < 4; i++) {
+        v.push_back(i);
+        EXPECT_TRUE(allocationIsInternal(v));
+    }
+
+    // then will fall back to external allocation
+    for (int i = 5; i < 50; i++) {
+        v.push_back(i);
+        EXPECT_FALSE(allocationIsInternal(v));
+    }
+}
+
+TEST(FatVector, shrink) {
+    FatVector<int, 10> v;
+    EXPECT_TRUE(allocationIsInternal(v));
+
+    // push into external alloc
+    v.resize(11);
+    EXPECT_FALSE(allocationIsInternal(v));
+
+    // shrinking back to internal alloc succeeds
+    // note that shrinking further will succeed, but is a waste
+    v.resize(10);
+    v.shrink_to_fit();
+    EXPECT_TRUE(allocationIsInternal(v));
+}
+
+TEST(FatVector, destructorInternal) {
+    int count = 0;
+    {
+        // push 1 into external allocation, verify destruction happens once
+        FatVector<TestUtils::SignalingDtor, 0> v;
+        v.emplace_back(&count);
+        EXPECT_FALSE(allocationIsInternal(v));
+        EXPECT_EQ(0, count);
+    }
+    EXPECT_EQ(1, count);
+}
+
+TEST(FatVector, destructorExternal) {
+    int count = 0;
+    {
+        // push 10 into internal allocation, verify 10 destructors called
+        FatVector<TestUtils::SignalingDtor, 10> v;
+        for (int i = 0; i < 10; i++) {
+            v.emplace_back(&count);
+            EXPECT_TRUE(allocationIsInternal(v));
+        }
+        EXPECT_EQ(0, count);
+    }
+    EXPECT_EQ(10, count);
+}
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
index 02cd77a..0f6b249 100644
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -17,6 +17,8 @@
 #include <gtest/gtest.h>
 #include <utils/LinearAllocator.h>
 
+#include <unit_tests/TestUtils.h>
+
 using namespace android;
 using namespace android::uirenderer;
 
@@ -25,27 +27,6 @@
     int two = 2;
 };
 
-class SignalingDtor {
-public:
-    SignalingDtor() {
-        mDestroyed = nullptr;
-    }
-    SignalingDtor(bool* destroyedSignal) {
-        mDestroyed = destroyedSignal;
-        *mDestroyed = false;
-    }
-    virtual ~SignalingDtor() {
-        if (mDestroyed) {
-            *mDestroyed = true;
-        }
-    }
-    void setSignal(bool* destroyedSignal) {
-        mDestroyed = destroyedSignal;
-    }
-private:
-    bool* mDestroyed;
-};
-
 TEST(LinearAllocator, alloc) {
     LinearAllocator la;
     EXPECT_EQ(0u, la.usedSize());
@@ -62,31 +43,31 @@
 }
 
 TEST(LinearAllocator, dtor) {
-    bool destroyed[10];
+    int destroyed[10] = { 0 };
     {
         LinearAllocator la;
         for (int i = 0; i < 5; i++) {
-            la.alloc<SignalingDtor>()->setSignal(destroyed + i);
+            la.alloc<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
             la.alloc<SimplePair>();
         }
         la.alloc(100);
         for (int i = 0; i < 5; i++) {
-            auto sd = new (la) SignalingDtor(destroyed + 5 + i);
+            auto sd = new (la) TestUtils::SignalingDtor(destroyed + 5 + i);
             la.autoDestroy(sd);
             new (la) SimplePair();
         }
         la.alloc(100);
         for (int i = 0; i < 10; i++) {
-            EXPECT_FALSE(destroyed[i]);
+            EXPECT_EQ(0, destroyed[i]);
         }
     }
     for (int i = 0; i < 10; i++) {
-        EXPECT_TRUE(destroyed[i]);
+        EXPECT_EQ(1, destroyed[i]);
     }
 }
 
 TEST(LinearAllocator, rewind) {
-    bool destroyed;
+    int destroyed = 0;
     {
         LinearAllocator la;
         auto addr = la.alloc(100);
@@ -94,17 +75,16 @@
         la.rewindIfLastAlloc(addr, 100);
         EXPECT_GT(16u, la.usedSize());
         size_t emptySize = la.usedSize();
-        auto sigdtor = la.alloc<SignalingDtor>();
+        auto sigdtor = la.alloc<TestUtils::SignalingDtor>();
         sigdtor->setSignal(&destroyed);
-        EXPECT_FALSE(destroyed);
+        EXPECT_EQ(0, destroyed);
         EXPECT_LE(emptySize, la.usedSize());
         la.rewindIfLastAlloc(sigdtor);
-        EXPECT_TRUE(destroyed);
+        EXPECT_EQ(1, destroyed);
         EXPECT_EQ(emptySize, la.usedSize());
-        destroyed = false;
     }
     // Checking for a double-destroy case
-    EXPECT_EQ(destroyed, false);
+    EXPECT_EQ(1, destroyed);
 }
 
 TEST(LinearStdAllocator, simpleAllocate) {
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index d02f89d..ffb575f 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -27,68 +27,67 @@
 namespace android {
 namespace uirenderer {
 
+
 /**
- * Class that redirects static operation dispatch to virtual methods on a Client class.
+ * Virtual class implemented by each test to redirect static operation / state transitions to
+ * virtual methods.
  *
- * The client is recreated for every op (so data cannot be persisted between operations), but the
- * virtual dispatch allows for default behaviors to be specified without enumerating each operation
- * for every test.
+ * Virtual dispatch allows for default behaviors to be specified (very common case in below tests),
+ * and allows Renderer vs Dispatching behavior to be merged.
  *
  * onXXXOp methods fail by default - tests should override ops they expect
+ * startLayer fails by default - tests should override if expected
  * startFrame/endFrame do nothing by default - tests should override to intercept
  */
-template<class CustomClient, class Arg>
-class TestReceiver {
+class TestRendererBase {
 public:
-#define CLIENT_METHOD(Type) \
-    virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); }
-    class Client {
-    public:
-        virtual ~Client() {};
-        MAP_OPS(CLIENT_METHOD)
+    virtual ~TestRendererBase() {}
+    virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
+    virtual void endLayer() { ADD_FAILURE(); }
+    virtual void startFrame(uint32_t width, uint32_t height) {}
+    virtual void endFrame() {}
 
-        virtual void startFrame(Arg& info) {}
-        virtual void endFrame(Arg& info) {}
-    };
+    // define virtual defaults for direct
+#define BASE_OP_METHOD(Type) \
+    virtual void on##Type(const Type&, const BakedOpState&) { ADD_FAILURE(); }
+    MAP_OPS(BASE_OP_METHOD)
+    int getIndex() { return mIndex; }
 
+protected:
+    int mIndex = 0;
+};
+
+/**
+ * Dispatches all static methods to similar formed methods on renderer, which fail by default but
+ * are overriden by subclasses per test.
+ */
+class TestDispatcher {
+public:
 #define DISPATCHER_METHOD(Type) \
-    static void on##Type(Arg& arg, const Type& op, const BakedOpState& state) { \
-        CustomClient client; client.on##Type(arg, op, state); \
+    static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \
+        renderer.on##Type(op, state); \
     }
-    MAP_OPS(DISPATCHER_METHOD)
-
-    static void startFrame(Arg& info) {
-        CustomClient client;
-        client.startFrame(info);
-    }
-
-    static void endFrame(Arg& info) {
-        CustomClient client;
-        client.endFrame(info);
-    }
+    MAP_OPS(DISPATCHER_METHOD);
 };
 
-class Info {
-public:
-    int index = 0;
-};
 
-// Receiver class which will fail if it receives any ops
-class FailReceiver : public TestReceiver<FailReceiver, Info>::Client {};
+class FailRenderer : public TestRendererBase {};
 
-class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client {
+class SimpleTestRenderer : public TestRendererBase {
 public:
-    void startFrame(Info& info) override {
-        EXPECT_EQ(0, info.index++);
+    void startFrame(uint32_t width, uint32_t height) override {
+        EXPECT_EQ(0, mIndex++);
+        EXPECT_EQ(100u, width);
+        EXPECT_EQ(200u, height);
     }
-    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, info.index++);
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, mIndex++);
     }
-    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(2, info.index++);
+    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(2, mIndex++);
     }
-    void endFrame(Info& info) override {
-        EXPECT_EQ(3, info.index++);
+    void endFrame() override {
+        EXPECT_EQ(3, mIndex++);
     }
 };
 TEST(OpReorderer, simple) {
@@ -97,12 +96,11 @@
         canvas.drawRect(0, 0, 100, 200, SkPaint());
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
-    OpReorderer reorderer;
-    reorderer.defer(200, 200, *dl);
+    OpReorderer reorderer(100, 200, *dl);
 
-    Info info;
-    reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info);
-    EXPECT_EQ(4, info.index); // 2 ops + start + end
+    SimpleTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
 }
 
 
@@ -113,22 +111,21 @@
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.restore();
     });
-    OpReorderer reorderer;
-    reorderer.defer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl);
 
-    Info info;
-    reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+    FailRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
 
 static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingReceiver : public TestReceiver<SimpleBatchingReceiver, Info>::Client {
+class SimpleBatchingTestRenderer : public TestRendererBase {
 public:
-    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_TRUE(info.index++ >= SIMPLE_BATCHING_LOOPS);
+    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
     }
-    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
-        EXPECT_TRUE(info.index++ < SIMPLE_BATCHING_LOOPS);
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
     }
 };
 TEST(OpReorderer, simpleBatching) {
@@ -146,18 +143,17 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer;
-    reorderer.defer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl);
 
-    Info info;
-    reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info);
-    EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
+    SimpleBatchingTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
 }
 
-class RenderNodeReceiver : public TestReceiver<RenderNodeReceiver, Info>::Client {
+class RenderNodeTestRenderer : public TestRendererBase {
 public:
-    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
-        switch(info.index++) {
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        switch(mIndex++) {
         case 0:
             EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
             EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
@@ -167,7 +163,7 @@
             EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
             break;
         default:
-            FAIL();
+            ADD_FAILURE();
         }
     }
 };
@@ -196,17 +192,16 @@
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(parent.get());
 
-    OpReorderer reorderer;
-    reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
 
-    Info info;
-    reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info);
+    RenderNodeTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-class ClippedReceiver : public TestReceiver<ClippedReceiver, Info>::Client {
+class ClippedTestRenderer : public TestRendererBase {
 public:
-    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(0, info.index++);
+    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(0, mIndex++);
         EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
         EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
         EXPECT_TRUE(state.computedState.transform.isIdentity());
@@ -221,19 +216,27 @@
     std::vector< sp<RenderNode> > nodes;
     nodes.push_back(node.get());
 
-    OpReorderer reorderer;
-    reorderer.defer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
+    OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
             200, 200, nodes);
 
-    Info info;
-    reorderer.replayBakedOps<TestReceiver<ClippedReceiver, Info>>(info);
+    ClippedTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
 
-class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client {
+class SaveLayerSimpleTestRenderer : public TestRendererBase {
 public:
-    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(0, info.index++);
+    OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+        EXPECT_EQ(0, mIndex++);
+        EXPECT_EQ(180u, width);
+        EXPECT_EQ(180u, height);
+        return nullptr;
+    }
+    void endLayer() override {
+        EXPECT_EQ(2, mIndex++);
+    }
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, mIndex++);
         EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
         EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
         EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
@@ -242,8 +245,8 @@
         expectedTransform.loadTranslate(-10, -10, 0);
         EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
     }
-    void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, info.index++);
+    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(3, mIndex++);
         EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
         EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
         EXPECT_TRUE(state.computedState.transform.isIdentity());
@@ -256,33 +259,61 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer;
-    reorderer.defer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl);
 
-    Info info;
-    reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info);
-    EXPECT_EQ(2, info.index);
+    SaveLayerSimpleTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
 }
 
 
-// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1
-class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client {
+/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
+ * - startLayer2, rect2 endLayer2
+ * - startLayer1, rect1, drawLayer2, endLayer1
+ * - startFrame, layerOp1, endFrame
+ */
+class SaveLayerNestedTestRenderer : public TestRendererBase {
 public:
-    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
-        const int index = info.index++;
+    OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+        const int index = mIndex++;
         if (index == 0) {
-            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
-        } else if (index == 1) {
-            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
-        } else { FAIL(); }
-    }
-    void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
-        const int index = info.index++;
-        if (index == 2) {
-            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+            EXPECT_EQ(400u, width);
+            EXPECT_EQ(400u, height);
+            return (OffscreenBuffer*) 0x400;
         } else if (index == 3) {
+            EXPECT_EQ(800u, width);
+            EXPECT_EQ(800u, height);
+            return (OffscreenBuffer*) 0x800;
+        } else { ADD_FAILURE(); }
+        return (OffscreenBuffer*) nullptr;
+    }
+    void endLayer() override {
+        int index = mIndex++;
+        EXPECT_TRUE(index == 2 || index == 6);
+    }
+    void startFrame(uint32_t width, uint32_t height) override {
+        EXPECT_EQ(7, mIndex++);
+    }
+    void endFrame() override {
+        EXPECT_EQ(9, mIndex++);
+    }
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        const int index = mIndex++;
+        if (index == 1) {
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+        } else if (index == 4) {
+            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+        } else { ADD_FAILURE(); }
+    }
+    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+        const int index = mIndex++;
+        if (index == 5) {
+            EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+        } else if (index == 8) {
+            EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
             EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
-        } else { FAIL(); }
+        } else { ADD_FAILURE(); }
     }
 };
 TEST(OpReorderer, saveLayerNested) {
@@ -299,12 +330,11 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer;
-    reorderer.defer(800, 800, *dl);
+    OpReorderer reorderer(800, 800, *dl);
 
-    Info info;
-    reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info);
-    EXPECT_EQ(4, info.index);
+    SaveLayerNestedTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(10, renderer.getIndex());
 }
 
 TEST(OpReorderer, saveLayerContentRejection) {
@@ -319,12 +349,11 @@
         canvas.restore();
         canvas.restore();
     });
-    OpReorderer reorderer;
-    reorderer.defer(200, 200, *dl);
-    Info info;
+    OpReorderer reorderer(200, 200, *dl);
 
+    FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
-    reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
 } // namespace uirenderer
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index c023123..e8cdf46 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -38,7 +38,7 @@
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
         canvas.restore();
     });
-    playbackOps(*dl, [](const RecordedOp& op) { FAIL(); });
+    playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
 }
 
 TEST(RecordingCanvas, testSimpleRectRecord) {
@@ -135,7 +135,7 @@
             // TODO: add asserts
             break;
         default:
-            FAIL();
+            ADD_FAILURE();
         }
     });
     EXPECT_EQ(3, count);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 99ecc9b..5b09fda 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -39,6 +39,24 @@
 
 class TestUtils {
 public:
+    class SignalingDtor {
+    public:
+        SignalingDtor()
+                : mSignal(nullptr) {}
+        SignalingDtor(int* signal)
+                : mSignal(signal) {}
+        void setSignal(int* signal) {
+            mSignal = signal;
+        }
+        ~SignalingDtor() {
+            if (mSignal) {
+                (*mSignal)++;
+            }
+        }
+    private:
+        int* mSignal;
+    };
+
     static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
         for (int i = 0; i < 16; i++) {
             if (!MathUtils::areEqual(a[i], b[i])) {
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
new file mode 100644
index 0000000..c3c16c5a
--- /dev/null
+++ b/libs/hwui/utils/FatVector.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ANDROID_FAT_VECTOR_H
+#define ANDROID_FAT_VECTOR_H
+
+#include "utils/Macros.h"
+
+#include <stddef.h>
+#include <type_traits>
+#include <utils/Log.h>
+
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+template <typename T, size_t SIZE>
+class InlineStdAllocator {
+public:
+    struct Allocation {
+        PREVENT_COPY_AND_ASSIGN(Allocation);
+    public:
+        Allocation() {};
+        // char array instead of T array, so memory is uninitialized, with no destructors run
+        char array[sizeof(T) * SIZE];
+        bool inUse = false;
+    };
+
+    typedef T value_type; // needed to implement std::allocator
+    typedef T* pointer; // needed to implement std::allocator
+
+    InlineStdAllocator(Allocation& allocation)
+            : mAllocation(allocation) {}
+    InlineStdAllocator(const InlineStdAllocator& other)
+            : mAllocation(other.mAllocation) {}
+    ~InlineStdAllocator() {}
+
+    T* allocate(size_t num, const void* = 0) {
+        if (!mAllocation.inUse && num <= SIZE) {
+            mAllocation.inUse = true;
+            return (T*) mAllocation.array;
+        } else {
+            return (T*) malloc(num * sizeof(T));
+        }
+    }
+
+    void deallocate(pointer p, size_t num) {
+        if (p == (T*)mAllocation.array) {
+            mAllocation.inUse = false;
+        } else {
+            // 'free' instead of delete here - destruction handled separately
+            free(p);
+        }
+    }
+    Allocation& mAllocation;
+};
+
+/**
+ * std::vector with SIZE elements preallocated into an internal buffer.
+ *
+ * Useful for avoiding the cost of malloc in cases where only SIZE or
+ * fewer elements are needed in the common case.
+ */
+template <typename T, size_t SIZE>
+class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> {
+public:
+    FatVector() : std::vector<T, InlineStdAllocator<T, SIZE>>(
+            InlineStdAllocator<T, SIZE>(mAllocation)) {
+        this->reserve(SIZE);
+    }
+private:
+    typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_FAT_VECTOR_H
diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp
index f51f5df..985f3fb 100644
--- a/libs/hwui/utils/NinePatchImpl.cpp
+++ b/libs/hwui/utils/NinePatchImpl.cpp
@@ -82,7 +82,7 @@
         }
     } else {
     SLOW_CASE:
-        canvas->drawBitmapRect(bitmap, &src, dst, &paint);
+        canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint);
     }
 }
 
diff --git a/libs/hwui/utils/TimeUtils.h b/libs/hwui/utils/TimeUtils.h
new file mode 100644
index 0000000..8d42d7e
--- /dev/null
+++ b/libs/hwui/utils/TimeUtils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef UTILS_TIMEUTILS_H
+#define UTILS_TIMEUTILS_H
+
+#include <utils/Timers.h>
+
+namespace android {
+namespace uirenderer {
+
+constexpr nsecs_t operator"" _ms (unsigned long long ms) {
+    return milliseconds_to_nanoseconds(ms);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* UTILS_TIMEUTILS_H */
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index bf3387b..4d0d1bd 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -78,32 +78,51 @@
      */
     public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
 
+    /**
+     * Bit mask for mFieldsMask indicating the presence of mAltitude.
+     */
+    private static final byte HAS_ALTITUDE_MASK = 1;
+    /**
+     * Bit mask for mFieldsMask indicating the presence of mSpeed.
+     */
+    private static final byte HAS_SPEED_MASK = 2;
+    /**
+     * Bit mask for mFieldsMask indicating the presence of mBearing.
+     */
+    private static final byte HAS_BEARING_MASK = 4;
+    /**
+     * Bit mask for mFieldsMask indicating the presence of mAccuracy.
+     */
+    private static final byte HAS_ACCURACY_MASK = 8;
+    /**
+     * Bit mask for mFieldsMask indicating location is from a mock provider.
+     */
+    private static final byte HAS_MOCK_PROVIDER_MASK = 16;
+
+    // Cached data to make bearing/distance computations more efficient for the case
+    // where distanceTo and bearingTo are called in sequence.  Assume this typically happens
+    // on the same thread for caching purposes.
+    private static ThreadLocal<BearingDistanceCache> sBearingDistanceCache
+            = new ThreadLocal<BearingDistanceCache>() {
+        @Override
+        protected BearingDistanceCache initialValue() {
+            return new BearingDistanceCache();
+        }
+    };
+
     private String mProvider;
     private long mTime = 0;
     private long mElapsedRealtimeNanos = 0;
     private double mLatitude = 0.0;
     private double mLongitude = 0.0;
-    private boolean mHasAltitude = false;
     private double mAltitude = 0.0f;
-    private boolean mHasSpeed = false;
     private float mSpeed = 0.0f;
-    private boolean mHasBearing = false;
     private float mBearing = 0.0f;
-    private boolean mHasAccuracy = false;
     private float mAccuracy = 0.0f;
     private Bundle mExtras = null;
-    private boolean mIsFromMockProvider = false;
 
-    // Cache the inputs and outputs of computeDistanceAndBearing
-    // so calls to distanceTo() and bearingTo() can share work
-    private double mLat1 = 0.0;
-    private double mLon1 = 0.0;
-    private double mLat2 = 0.0;
-    private double mLon2 = 0.0;
-    private float mDistance = 0.0f;
-    private float mInitialBearing = 0.0f;
-    // Scratchpad
-    private final float[] mResults = new float[2];
+    // A bitmask of fields present in this object (see HAS_* constants defined above).
+    private byte mFieldsMask = 0;
 
     /**
      * Construct a new Location with a named provider.
@@ -131,18 +150,14 @@
         mProvider = l.mProvider;
         mTime = l.mTime;
         mElapsedRealtimeNanos = l.mElapsedRealtimeNanos;
+        mFieldsMask = l.mFieldsMask;
         mLatitude = l.mLatitude;
         mLongitude = l.mLongitude;
-        mHasAltitude = l.mHasAltitude;
         mAltitude = l.mAltitude;
-        mHasSpeed = l.mHasSpeed;
         mSpeed = l.mSpeed;
-        mHasBearing = l.mHasBearing;
         mBearing = l.mBearing;
-        mHasAccuracy = l.mHasAccuracy;
         mAccuracy = l.mAccuracy;
         mExtras = (l.mExtras == null) ? null : new Bundle(l.mExtras);
-        mIsFromMockProvider = l.mIsFromMockProvider;
     }
 
     /**
@@ -152,18 +167,14 @@
         mProvider = null;
         mTime = 0;
         mElapsedRealtimeNanos = 0;
+        mFieldsMask = 0;
         mLatitude = 0;
         mLongitude = 0;
-        mHasAltitude = false;
         mAltitude = 0;
-        mHasSpeed = false;
         mSpeed = 0;
-        mHasBearing = false;
         mBearing = 0;
-        mHasAccuracy = false;
         mAccuracy = 0;
         mExtras = null;
-        mIsFromMockProvider = false;
     }
 
     /**
@@ -257,11 +268,13 @@
             int deg = Integer.parseInt(degrees);
             double min;
             double sec = 0.0;
+            boolean secPresent = false;
 
             if (st.hasMoreTokens()) {
                 min = Integer.parseInt(minutes);
                 String seconds = st.nextToken();
                 sec = Double.parseDouble(seconds);
+                secPresent = true;
             } else {
                 min = Double.parseDouble(minutes);
             }
@@ -273,11 +286,15 @@
             if ((deg < 0.0) || (deg > 179 && !isNegative180)) {
                 throw new IllegalArgumentException("coordinate=" + coordinate);
             }
-            if (min < 0 || min > 59) {
+
+            // min must be in [0, 59] if seconds are present, otherwise [0.0, 60.0)
+            if (min < 0 || min >= 60 || (secPresent && (min > 59))) {
                 throw new IllegalArgumentException("coordinate=" +
                         coordinate);
             }
-            if (sec < 0 || sec > 59) {
+
+            // sec must be in [0.0, 60.0)
+            if (sec < 0 || sec >= 60) {
                 throw new IllegalArgumentException("coordinate=" +
                         coordinate);
             }
@@ -291,7 +308,7 @@
     }
 
     private static void computeDistanceAndBearing(double lat1, double lon1,
-        double lat2, double lon2, float[] results) {
+        double lat2, double lon2, BearingDistanceCache results) {
         // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
         // using the "Inverse Formula" (section 4)
 
@@ -376,19 +393,19 @@
         }
 
         float distance = (float) (b * A * (sigma - deltaSigma));
-        results[0] = distance;
-        if (results.length > 1) {
-            float initialBearing = (float) Math.atan2(cosU2 * sinLambda,
-                cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
-            initialBearing *= 180.0 / Math.PI;
-            results[1] = initialBearing;
-            if (results.length > 2) {
-                float finalBearing = (float) Math.atan2(cosU1 * sinLambda,
-                    -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda);
-                finalBearing *= 180.0 / Math.PI;
-                results[2] = finalBearing;
-            }
-        }
+        results.mDistance = distance;
+        float initialBearing = (float) Math.atan2(cosU2 * sinLambda,
+            cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
+        initialBearing *= 180.0 / Math.PI;
+        results.mInitialBearing = initialBearing;
+        float finalBearing = (float) Math.atan2(cosU1 * sinLambda,
+                -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda);
+        finalBearing *= 180.0 / Math.PI;
+        results.mFinalBearing = finalBearing;
+        results.mLat1 = lat1;
+        results.mLat2 = lat2;
+        results.mLon1 = lon1;
+        results.mLon2 = lon2;
     }
 
     /**
@@ -414,8 +431,16 @@
         if (results == null || results.length < 1) {
             throw new IllegalArgumentException("results is null or has length < 1");
         }
+        BearingDistanceCache cache = sBearingDistanceCache.get();
         computeDistanceAndBearing(startLatitude, startLongitude,
-            endLatitude, endLongitude, results);
+                endLatitude, endLongitude, cache);
+        results[0] = cache.mDistance;
+        if (results.length > 1) {
+            results[1] = cache.mInitialBearing;
+            if (results.length > 2) {
+                results[2] = cache.mFinalBearing;
+            }
+        }
     }
 
     /**
@@ -427,21 +452,14 @@
      * @return the approximate distance in meters
      */
     public float distanceTo(Location dest) {
+        BearingDistanceCache cache = sBearingDistanceCache.get();
         // See if we already have the result
-        synchronized (mResults) {
-            if (mLatitude != mLat1 || mLongitude != mLon1 ||
-                dest.mLatitude != mLat2 || dest.mLongitude != mLon2) {
-                computeDistanceAndBearing(mLatitude, mLongitude,
-                    dest.mLatitude, dest.mLongitude, mResults);
-                mLat1 = mLatitude;
-                mLon1 = mLongitude;
-                mLat2 = dest.mLatitude;
-                mLon2 = dest.mLongitude;
-                mDistance = mResults[0];
-                mInitialBearing = mResults[1];
-            }
-            return mDistance;
+        if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 ||
+            dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) {
+            computeDistanceAndBearing(mLatitude, mLongitude,
+                dest.mLatitude, dest.mLongitude, cache);
         }
+        return cache.mDistance;
     }
 
     /**
@@ -455,21 +473,14 @@
      * @return the initial bearing in degrees
      */
     public float bearingTo(Location dest) {
-        synchronized (mResults) {
-            // See if we already have the result
-            if (mLatitude != mLat1 || mLongitude != mLon1 ||
-                            dest.mLatitude != mLat2 || dest.mLongitude != mLon2) {
-                computeDistanceAndBearing(mLatitude, mLongitude,
-                    dest.mLatitude, dest.mLongitude, mResults);
-                mLat1 = mLatitude;
-                mLon1 = mLongitude;
-                mLat2 = dest.mLatitude;
-                mLon2 = dest.mLongitude;
-                mDistance = mResults[0];
-                mInitialBearing = mResults[1];
-            }
-            return mInitialBearing;
+        BearingDistanceCache cache = sBearingDistanceCache.get();
+        // See if we already have the result
+        if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 ||
+                        dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) {
+            computeDistanceAndBearing(mLatitude, mLongitude,
+                dest.mLatitude, dest.mLongitude, cache);
         }
+        return cache.mInitialBearing;
     }
 
     /**
@@ -585,7 +596,7 @@
      * True if this location has an altitude.
      */
     public boolean hasAltitude() {
-        return mHasAltitude;
+        return (mFieldsMask & HAS_ALTITUDE_MASK) != 0;
     }
 
     /**
@@ -605,7 +616,7 @@
      */
     public void setAltitude(double altitude) {
         mAltitude = altitude;
-        mHasAltitude = true;
+        mFieldsMask |= HAS_ALTITUDE_MASK;
     }
 
     /**
@@ -616,14 +627,14 @@
      */
     public void removeAltitude() {
         mAltitude = 0.0f;
-        mHasAltitude = false;
+        mFieldsMask &= ~HAS_ALTITUDE_MASK;
     }
 
     /**
      * True if this location has a speed.
      */
     public boolean hasSpeed() {
-        return mHasSpeed;
+        return (mFieldsMask & HAS_SPEED_MASK) != 0;
     }
 
     /**
@@ -642,7 +653,7 @@
      */
     public void setSpeed(float speed) {
         mSpeed = speed;
-        mHasSpeed = true;
+        mFieldsMask |= HAS_SPEED_MASK;
     }
 
     /**
@@ -653,14 +664,14 @@
      */
     public void removeSpeed() {
         mSpeed = 0.0f;
-        mHasSpeed = false;
+        mFieldsMask &= ~HAS_SPEED_MASK;
     }
 
     /**
      * True if this location has a bearing.
      */
     public boolean hasBearing() {
-        return mHasBearing;
+        return (mFieldsMask & HAS_BEARING_MASK) != 0;
     }
 
     /**
@@ -692,7 +703,7 @@
             bearing -= 360.0f;
         }
         mBearing = bearing;
-        mHasBearing = true;
+        mFieldsMask |= HAS_BEARING_MASK;
     }
 
     /**
@@ -703,7 +714,7 @@
      */
     public void removeBearing() {
         mBearing = 0.0f;
-        mHasBearing = false;
+        mFieldsMask &= ~HAS_BEARING_MASK;
     }
 
     /**
@@ -713,7 +724,7 @@
      * accuracy.
      */
     public boolean hasAccuracy() {
-        return mHasAccuracy;
+        return (mFieldsMask & HAS_ACCURACY_MASK) != 0;
     }
 
     /**
@@ -751,7 +762,7 @@
      */
     public void setAccuracy(float accuracy) {
         mAccuracy = accuracy;
-        mHasAccuracy = true;
+        mFieldsMask |= HAS_ACCURACY_MASK;
     }
 
     /**
@@ -762,7 +773,7 @@
      */
     public void removeAccuracy() {
         mAccuracy = 0.0f;
-        mHasAccuracy = false;
+        mFieldsMask &= ~HAS_ACCURACY_MASK;
     }
 
     /**
@@ -780,7 +791,7 @@
     @SystemApi
     public boolean isComplete() {
         if (mProvider == null) return false;
-        if (!mHasAccuracy) return false;
+        if (!hasAccuracy()) return false;
         if (mTime == 0) return false;
         if (mElapsedRealtimeNanos == 0) return false;
         return true;
@@ -798,8 +809,8 @@
     @SystemApi
     public void makeComplete() {
         if (mProvider == null) mProvider = "?";
-        if (!mHasAccuracy) {
-            mHasAccuracy = true;
+        if (!hasAccuracy()) {
+            mFieldsMask |= HAS_ACCURACY_MASK;
             mAccuracy = 100.0f;
         }
         if (mTime == 0) mTime = System.currentTimeMillis();
@@ -838,7 +849,7 @@
         s.append("Location[");
         s.append(mProvider);
         s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude));
-        if (mHasAccuracy) s.append(String.format(" acc=%.0f", mAccuracy));
+        if (hasAccuracy()) s.append(String.format(" acc=%.0f", mAccuracy));
         else s.append(" acc=???");
         if (mTime == 0) {
             s.append(" t=?!?");
@@ -849,10 +860,10 @@
             s.append(" et=");
             TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s);
         }
-        if (mHasAltitude) s.append(" alt=").append(mAltitude);
-        if (mHasSpeed) s.append(" vel=").append(mSpeed);
-        if (mHasBearing) s.append(" bear=").append(mBearing);
-        if (mIsFromMockProvider) s.append(" mock");
+        if (hasAltitude()) s.append(" alt=").append(mAltitude);
+        if (hasSpeed()) s.append(" vel=").append(mSpeed);
+        if (hasBearing()) s.append(" bear=").append(mBearing);
+        if (isFromMockProvider()) s.append(" mock");
 
         if (mExtras != null) {
             s.append(" {").append(mExtras).append('}');
@@ -873,18 +884,14 @@
             Location l = new Location(provider);
             l.mTime = in.readLong();
             l.mElapsedRealtimeNanos = in.readLong();
+            l.mFieldsMask = in.readByte();
             l.mLatitude = in.readDouble();
             l.mLongitude = in.readDouble();
-            l.mHasAltitude = in.readInt() != 0;
             l.mAltitude = in.readDouble();
-            l.mHasSpeed = in.readInt() != 0;
             l.mSpeed = in.readFloat();
-            l.mHasBearing = in.readInt() != 0;
             l.mBearing = in.readFloat();
-            l.mHasAccuracy = in.readInt() != 0;
             l.mAccuracy = in.readFloat();
             l.mExtras = in.readBundle();
-            l.mIsFromMockProvider = in.readInt() != 0;
             return l;
         }
 
@@ -904,18 +911,14 @@
         parcel.writeString(mProvider);
         parcel.writeLong(mTime);
         parcel.writeLong(mElapsedRealtimeNanos);
+        parcel.writeByte(mFieldsMask);
         parcel.writeDouble(mLatitude);
         parcel.writeDouble(mLongitude);
-        parcel.writeInt(mHasAltitude ? 1 : 0);
         parcel.writeDouble(mAltitude);
-        parcel.writeInt(mHasSpeed ? 1 : 0);
         parcel.writeFloat(mSpeed);
-        parcel.writeInt(mHasBearing ? 1 : 0);
         parcel.writeFloat(mBearing);
-        parcel.writeInt(mHasAccuracy ? 1 : 0);
         parcel.writeFloat(mAccuracy);
         parcel.writeBundle(mExtras);
-        parcel.writeInt(mIsFromMockProvider? 1 : 0);
     }
 
     /**
@@ -940,7 +943,7 @@
      * Attaches an extra {@link Location} to this Location.
      *
      * @param key the key associated with the Location extra
-     * @param location the Location to attach
+     * @param value the Location to attach
      * @hide
      */
     public void setExtraLocation(String key, Location value) {
@@ -956,7 +959,7 @@
      * @return true if this Location came from a mock provider, false otherwise
      */
     public boolean isFromMockProvider() {
-        return mIsFromMockProvider;
+        return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0;
     }
 
     /**
@@ -967,6 +970,24 @@
      */
     @SystemApi
     public void setIsFromMockProvider(boolean isFromMockProvider) {
-        mIsFromMockProvider = isFromMockProvider;
+        if (isFromMockProvider) {
+            mFieldsMask |= HAS_MOCK_PROVIDER_MASK;
+        } else {
+            mFieldsMask &= ~HAS_MOCK_PROVIDER_MASK;
+        }
+    }
+
+    /**
+     * Caches data used to compute distance and bearing (so successive calls to {@link #distanceTo}
+     * and {@link #bearingTo} don't duplicate work.
+     */
+    private static class BearingDistanceCache {
+        private double mLat1 = 0.0;
+        private double mLon1 = 0.0;
+        private double mLat2 = 0.0;
+        private double mLon2 = 0.0;
+        private float mDistance = 0.0f;
+        private float mInitialBearing = 0.0f;
+        private float mFinalBearing = 0.0f;
     }
 }
diff --git a/location/tests/locationtests/src/android/location/LocationTest.java b/location/tests/locationtests/src/android/location/LocationTest.java
index 847ac7a..dc8d0f7 100644
--- a/location/tests/locationtests/src/android/location/LocationTest.java
+++ b/location/tests/locationtests/src/android/location/LocationTest.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import junit.framework.TestCase;
@@ -225,6 +226,40 @@
         assertEquals(message, loc.getBearing(), 0, 0);
     }
 
+    public void testParcel() {
+        final double expectedLat = 33;
+        final double expectedLon = -122;
+        final float expectedAccuracy = 15;
+        final float expectedSpeed = 5;
+        Location loc = new Location("test");
+        loc.setLatitude(expectedLat);
+        loc.setLongitude(expectedLon);
+        loc.setAccuracy(expectedAccuracy);
+        loc.setSpeed(expectedSpeed);
+
+        // Serialize location object into bytes via parcelable capability
+        Parcel parcel = Parcel.obtain();
+        loc.writeToParcel(parcel, 0);
+        byte[] rawBytes = parcel.marshall();
+        parcel.recycle();
+
+        // Turn the bytes back into a location object
+        parcel = Parcel.obtain();
+        parcel.unmarshall(rawBytes, 0, rawBytes.length);
+        parcel.setDataPosition(0);
+        Location deserialized = Location.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertEquals(expectedLat, deserialized.getLatitude());
+        assertEquals(expectedLon, deserialized.getLongitude());
+        assertEquals(expectedAccuracy, deserialized.getAccuracy());
+        assertTrue(deserialized.hasAccuracy());
+        assertEquals(expectedSpeed, deserialized.getSpeed());
+        assertTrue(deserialized.hasSpeed());
+        assertFalse(deserialized.hasBearing());
+        assertFalse(deserialized.hasAltitude());
+        assertFalse(deserialized.isFromMockProvider());
+    }
 }
 
 
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index ba867e1..e0a8026 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -249,7 +249,7 @@
      */
     public @NonNull String getRoot() {
         if (!isConnected()) {
-            throw new IllegalStateException("getSessionToken() called while not connected (state="
+            throw new IllegalStateException("getRoot() called while not connected (state="
                     + getStateLabel(mState) + ")");
         }
         return mRootId;
@@ -776,68 +776,88 @@
      */
     private class MediaServiceConnection implements ServiceConnection {
         @Override
-        public void onServiceConnected(ComponentName name, IBinder binder) {
-            if (DBG) {
-                Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
-                        + " binder=" + binder);
-                dump();
-            }
+        public void onServiceConnected(final ComponentName name, final IBinder binder) {
+            postOrRun(new Runnable() {
+                @Override
+                public void run() {
+                    if (DBG) {
+                        Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
+                                + " binder=" + binder);
+                        dump();
+                    }
 
-            // Make sure we are still the current connection, and that they haven't called
-            // disconnect().
-            if (!isCurrent("onServiceConnected")) {
-                return;
-            }
+                    // Make sure we are still the current connection, and that they haven't called
+                    // disconnect().
+                    if (!isCurrent("onServiceConnected")) {
+                        return;
+                    }
 
-            // Save their binder
-            mServiceBinder = IMediaBrowserService.Stub.asInterface(binder);
+                    // Save their binder
+                    mServiceBinder = IMediaBrowserService.Stub.asInterface(binder);
 
-            // We make a new mServiceCallbacks each time we connect so that we can drop
-            // responses from previous connections.
-            mServiceCallbacks = getNewServiceCallbacks();
-            mState = CONNECT_STATE_CONNECTING;
+                    // We make a new mServiceCallbacks each time we connect so that we can drop
+                    // responses from previous connections.
+                    mServiceCallbacks = getNewServiceCallbacks();
+                    mState = CONNECT_STATE_CONNECTING;
 
-            // Call connect, which is async. When we get a response from that we will
-            // say that we're connected.
-            try {
-                if (DBG) {
-                    Log.d(TAG, "ServiceCallbacks.onConnect...");
-                    dump();
+                    // Call connect, which is async. When we get a response from that we will
+                    // say that we're connected.
+                    try {
+                        if (DBG) {
+                            Log.d(TAG, "ServiceCallbacks.onConnect...");
+                            dump();
+                        }
+                        mServiceBinder.connect(mContext.getPackageName(), mRootHints,
+                                mServiceCallbacks);
+                    } catch (RemoteException ex) {
+                        // Connect failed, which isn't good. But the auto-reconnect on the service
+                        // will take over and we will come back.  We will also get the
+                        // onServiceDisconnected, which has all the cleanup code.  So let that do
+                        // it.
+                        Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
+                        if (DBG) {
+                            Log.d(TAG, "ServiceCallbacks.onConnect...");
+                            dump();
+                        }
+                    }
                 }
-                mServiceBinder.connect(mContext.getPackageName(), mRootHints, mServiceCallbacks);
-            } catch (RemoteException ex) {
-                // Connect failed, which isn't good. But the auto-reconnect on the service
-                // will take over and we will come back.  We will also get the
-                // onServiceDisconnected, which has all the cleanup code.  So let that do it.
-                Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
-                if (DBG) {
-                    Log.d(TAG, "ServiceCallbacks.onConnect...");
-                    dump();
-                }
-            }
+            });
         }
 
         @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (DBG) {
-                Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
-                        + " this=" + this + " mServiceConnection=" + mServiceConnection);
-                dump();
+        public void onServiceDisconnected(final ComponentName name) {
+            postOrRun(new Runnable() {
+                @Override
+                public void run() {
+                    if (DBG) {
+                        Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
+                                + " this=" + this + " mServiceConnection=" + mServiceConnection);
+                        dump();
+                    }
+
+                    // Make sure we are still the current connection, and that they haven't called
+                    // disconnect().
+                    if (!isCurrent("onServiceDisconnected")) {
+                        return;
+                    }
+
+                    // Clear out what we set in onServiceConnected
+                    mServiceBinder = null;
+                    mServiceCallbacks = null;
+
+                    // And tell the app that it's suspended.
+                    mState = CONNECT_STATE_SUSPENDED;
+                    mCallback.onConnectionSuspended();
+                }
+            });
+        }
+
+        private void postOrRun(Runnable r) {
+            if (Thread.currentThread() == mHandler.getLooper().getThread()) {
+                r.run();
+            } else {
+                mHandler.post(r);
             }
-
-            // Make sure we are still the current connection, and that they haven't called
-            // disconnect().
-            if (!isCurrent("onServiceDisconnected")) {
-                return;
-            }
-
-            // Clear out what we set in onServiceConnected
-            mServiceBinder = null;
-            mServiceCallbacks = null;
-
-            // And tell the app that it's suspended.
-            mState = CONNECT_STATE_SUSPENDED;
-            mCallback.onConnectionSuspended();
         }
 
         /**
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 45fb579..93dbf3f 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -180,7 +180,8 @@
             Log.e(TAG, "could not open device " + info);
         } else {
             ...
-        }, new Handler(Looper.getMainLooper())
+        }
+    }, new Handler(Looper.getMainLooper())
     );
 </pre>
 
diff --git a/opengl/java/android/opengl/GLES31.java b/opengl/java/android/opengl/GLES31.java
index 3cbaa60..805930e 100644
--- a/opengl/java/android/opengl/GLES31.java
+++ b/opengl/java/android/opengl/GLES31.java
@@ -24,9 +24,14 @@
 
     public static final int GL_VERTEX_SHADER_BIT                            = 0x00000001;
     public static final int GL_FRAGMENT_SHADER_BIT                          = 0x00000002;
+    public static final int GL_COMPUTE_SHADER_BIT                           = 0x00000020;
+    public static final int GL_ALL_SHADER_BITS                              = -1; // 0xFFFFFFFF
+
+    public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT              = 0x00000001;
+    public static final int GL_ELEMENT_ARRAY_BARRIER_BIT                    = 0x00000002;
     public static final int GL_UNIFORM_BARRIER_BIT                          = 0x00000004;
     public static final int GL_TEXTURE_FETCH_BARRIER_BIT                    = 0x00000008;
-    public static final int GL_COMPUTE_SHADER_BIT                           = 0x00000020;
+    public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT              = 0x00000020;
     public static final int GL_COMMAND_BARRIER_BIT                          = 0x00000040;
     public static final int GL_PIXEL_BUFFER_BARRIER_BIT                     = 0x00000080;
     public static final int GL_TEXTURE_UPDATE_BARRIER_BIT                   = 0x00000100;
@@ -35,7 +40,8 @@
     public static final int GL_TRANSFORM_FEEDBACK_BARRIER_BIT               = 0x00000800;
     public static final int GL_ATOMIC_COUNTER_BARRIER_BIT                   = 0x00001000;
     public static final int GL_SHADER_STORAGE_BARRIER_BIT                   = 0x00002000;
-    public static final int GL_ALL_SHADER_BITS                              = -1; // 0xFFFFFFFF
+    public static final int GL_ALL_BARRIER_BITS                             = -1; // 0xFFFFFFFF
+
 
     public static final int GL_TEXTURE_WIDTH                                = 0x1000;
     public static final int GL_TEXTURE_HEIGHT                               = 0x1001;
diff --git a/packages/DocumentsUI/res/values/config.xml b/packages/DocumentsUI/res/values/config.xml
index 73538278..ad419aa 100644
--- a/packages/DocumentsUI/res/values/config.xml
+++ b/packages/DocumentsUI/res/values/config.xml
@@ -16,6 +16,6 @@
 
 <resources>
     <bool name="productivity_device">true</bool>
-    <!-- intentionally unset. Vendors should set this in an overlay. -->
+    <!-- Intentionally unset. Vendors should set this in an overlay. -->
     <string name="trusted_quick_viewer_package"></string>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 45a8907..a09a22a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -17,7 +17,6 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.Shared.DEBUG;
-import static com.android.documentsui.State.ACTION_BROWSE;
 import static com.android.documentsui.State.ACTION_CREATE;
 import static com.android.documentsui.State.ACTION_MANAGE;
 import static com.android.documentsui.State.MODE_GRID;
@@ -55,7 +54,6 @@
 import android.os.Looper;
 import android.os.OperationCanceledException;
 import android.os.Parcelable;
-import android.os.SystemProperties;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.support.annotation.Nullable;
@@ -95,6 +93,7 @@
 import com.android.documentsui.MultiSelectManager.Selection;
 import com.android.documentsui.ProviderExecutor.Preemptable;
 import com.android.documentsui.RecentsProvider.StateColumns;
+import com.android.documentsui.dirlist.FragmentTuner;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
@@ -341,7 +340,7 @@
         mType = getArguments().getInt(EXTRA_TYPE);
         mStateKey = buildStateKey(root, doc);
 
-        mFragmentTuner = pickFragmentTuner(state);
+        mFragmentTuner = FragmentTuner.pick(state);
         mClipper = new DocumentClipper(context);
 
         if (mType == TYPE_RECENT_OPEN) {
@@ -429,7 +428,6 @@
         // Kick off loader at least once
         getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
 
-        mFragmentTuner.afterActivityCreated(this);
         updateDisplayState();
     }
 
@@ -1638,21 +1636,6 @@
         }
     }
 
-    private FragmentTuner pickFragmentTuner(final State state) {
-        return state.action == ACTION_BROWSE
-                ? new FilesTuner()
-                : new DefaultTuner(state.action);
-    }
-
-    /**
-     * Interface for specializing the Fragment for the "host" Activity.
-     * Feel free to expand the role of this class to handle other specializations.
-     */
-    private interface FragmentTuner {
-        void updateActionMenu(Menu menu, int dirType, boolean canDelete);
-        void afterActivityCreated(DirectoryFragment fragment);
-    }
-
     /**
      * Abstract task providing support for loading documents *off*
      * the main thread. And if it isn't obvious, creating a list
@@ -1673,65 +1656,6 @@
         abstract void onDocumentsReady(List<DocumentInfo> docs);
     }
 
-    /**
-     * Provides support for Platform specific specializations of DirectoryFragment.
-     */
-    private static final class DefaultTuner implements FragmentTuner {
-
-        private final boolean mManaging;
-
-        public DefaultTuner(int action) {
-            mManaging = (action == ACTION_MANAGE);
-        }
-
-        @Override
-        public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
-            boolean copyEnabled = mManaging && dirType != TYPE_RECENT_OPEN;
-            // TODO: The selection needs to be deletable.
-            boolean moveEnabled =
-                    SystemProperties.getBoolean("debug.documentsui.enable_move", false);
-            menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled);
-
-            final MenuItem open = menu.findItem(R.id.menu_open);
-            final MenuItem share = menu.findItem(R.id.menu_share);
-            final MenuItem delete = menu.findItem(R.id.menu_delete);
-            final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
-            final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
-
-            open.setVisible(!mManaging);
-            share.setVisible(mManaging);
-            delete.setVisible(mManaging && canDelete);
-            copyTo.setVisible(copyEnabled);
-            copyTo.setEnabled(copyEnabled);
-            moveTo.setVisible(moveEnabled);
-            moveTo.setEnabled(moveEnabled);
-        }
-
-        @Override
-        public void afterActivityCreated(DirectoryFragment fragment) {}
-    }
-
-    /**
-     * Provides support for Files activity specific specializations of DirectoryFragment.
-     */
-    private static final class FilesTuner implements FragmentTuner {
-        @Override
-        public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
-
-            menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(dirType != TYPE_RECENT_OPEN);
-
-            menu.findItem(R.id.menu_share).setVisible(true);
-            menu.findItem(R.id.menu_delete).setVisible(canDelete);
-
-            menu.findItem(R.id.menu_open).setVisible(false);
-            menu.findItem(R.id.menu_copy_to).setVisible(true);
-            menu.findItem(R.id.menu_move_to).setVisible(true);
-        }
-
-        @Override
-        public void afterActivityCreated(DirectoryFragment fragment) {}
-    }
-
     boolean isSelected(int position) {
         return mSelectionManager.getSelection().contains(position);
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 3b985ec..627ba75 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -334,12 +334,12 @@
                 startActivity(intent);
                 return;
             } catch (SecurityException e) {
-                // carry on to regular view mode.
+                // Carry on to regular view mode.
                 Log.e(TAG, "Caught security error: " + e.getLocalizedMessage());
             }
         }
 
-        // fallback to traditional VIEW action...
+        // Fallback to traditional VIEW action...
         intent = new Intent(Intent.ACTION_VIEW);
         intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         intent.setData(doc.derivedUri);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Menus.java b/packages/DocumentsUI/src/com/android/documentsui/Menus.java
index 3f43a3d..5277d2b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Menus.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Menus.java
@@ -19,14 +19,14 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
-final class Menus {
+public final class Menus {
 
     private Menus() {}
 
     /**
      * Disables hidden menu items so that they are not invokable via command shortcuts
      */
-    static void disableHiddenItems(Menu menu, MenuItem... exclusions) {
+    public static void disableHiddenItems(Menu menu, MenuItem... exclusions) {
         for (int i = 0; i < menu.size(); i++) {
             MenuItem item = menu.getItem(i);
             if (item.isVisible()) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index 6a424a6..605c530 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -92,6 +92,8 @@
                 intent.setClipData(mClipData);
 
                 return intent;
+            } else {
+                Log.e(TAG, "Can't resolve trusted quick view package: " + trustedPkg);
             }
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 0dcd02d..d75b6fd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -287,33 +287,24 @@
             super(context, 0);
 
             final List<RootItem> libraries = new ArrayList<>();
-            final List<RootItem> clouds = new ArrayList<>();
-            final List<RootItem> locals = new ArrayList<>();
+            final List<RootItem> others = new ArrayList<>();
 
-            for (RootInfo root : roots) {
-                RootItem item = new RootItem(root);
-                switch (root.derivedType) {
-                    case RootInfo.TYPE_LOCAL:
-                        locals.add(item);
-                        break;
-                    case RootInfo.TYPE_CLOUD:
-                        clouds.add(item);
-                        break;
-                    default:
-                        libraries.add(item);
-                        break;
+            for (final RootInfo root : roots) {
+                final RootItem item = new RootItem(root);
+                if (root.isLibrary()) {
+                    libraries.add(item);
+                } else {
+                    others.add(item);
                 }
             }
 
             final RootComparator comp = new RootComparator();
             Collections.sort(libraries, comp);
-            Collections.sort(locals, comp);
-            Collections.sort(clouds, comp);
+            Collections.sort(others, comp);
 
             addAll(libraries);
             add(new SpacerItem());
-            addAll(locals);
-            addAll(clouds);
+            addAll(others);
 
             if (includeApps != null) {
                 final PackageManager pm = context.getPackageManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
new file mode 100644
index 0000000..ca85cff
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.documentsui.State.ACTION_BROWSE;
+import static com.android.documentsui.State.ACTION_MANAGE;
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.os.SystemProperties;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.documentsui.DirectoryFragment;
+import com.android.documentsui.Menus;
+import com.android.documentsui.R;
+import com.android.documentsui.State;
+
+/**
+ * Providers support for specializing the DirectoryFragment to the "host" Activity.
+ * Feel free to expand the role of this class to handle other specializations.
+ */
+public abstract class FragmentTuner {
+    public static FragmentTuner pick(State state) {
+        switch (state.action) {
+            case ACTION_BROWSE:
+                return new FilesTuner();
+            case ACTION_MANAGE:
+                return new ManageTuner();
+            default:
+                return new DocumentsTuner();
+        }
+    }
+
+    public abstract void updateActionMenu(Menu menu, int dirType, boolean canDelete);
+
+    /**
+     * Provides support for Platform specific specializations of DirectoryFragment.
+     */
+    private static final class DocumentsTuner extends FragmentTuner {
+        @Override
+        public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+
+            boolean copyEnabled = dirType != DirectoryFragment.TYPE_RECENT_OPEN;
+            boolean moveEnabled =
+                    SystemProperties.getBoolean("debug.documentsui.enable_move", false);
+            menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled);
+
+            final MenuItem open = menu.findItem(R.id.menu_open);
+            final MenuItem share = menu.findItem(R.id.menu_share);
+            final MenuItem delete = menu.findItem(R.id.menu_delete);
+            final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
+            final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+
+            open.setVisible(true);
+            share.setVisible(false);
+            delete.setVisible(false);
+            copyTo.setVisible(copyEnabled);
+            copyTo.setEnabled(copyEnabled);
+            moveTo.setVisible(moveEnabled);
+            moveTo.setEnabled(moveEnabled);
+        }
+    }
+
+    /**
+     * Provides support for Platform specific specializations of DirectoryFragment.
+     */
+    private static final class ManageTuner extends FragmentTuner {
+
+        @Override
+        public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+            checkArgument(dirType != DirectoryFragment.TYPE_RECENT_OPEN);
+
+            boolean moveEnabled =
+                    SystemProperties.getBoolean("debug.documentsui.enable_move", false);
+            menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(true);
+
+            final MenuItem open = menu.findItem(R.id.menu_open);
+            final MenuItem share = menu.findItem(R.id.menu_share);
+            final MenuItem delete = menu.findItem(R.id.menu_delete);
+            final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
+            final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+
+            open.setVisible(false);
+            share.setVisible(false);
+            delete.setVisible(canDelete);
+            copyTo.setVisible(true);
+            copyTo.setEnabled(true);
+            moveTo.setVisible(moveEnabled);
+            moveTo.setEnabled(moveEnabled);
+        }
+    }
+
+    /**
+     * Provides support for Files activity specific specializations of DirectoryFragment.
+     */
+    private static final class FilesTuner extends FragmentTuner {
+        @Override
+        public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+
+            MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
+            MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
+            copy.setEnabled(dirType != DirectoryFragment.TYPE_RECENT_OPEN);
+
+            menu.findItem(R.id.menu_share).setVisible(true);
+            menu.findItem(R.id.menu_delete).setVisible(canDelete);
+
+            menu.findItem(R.id.menu_open).setVisible(false);
+            menu.findItem(R.id.menu_copy_to).setVisible(true);
+            menu.findItem(R.id.menu_move_to).setVisible(true);
+
+            Menus.disableHiddenItems(menu, copy, paste);
+        }
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 17e6a80..501392c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -51,7 +51,8 @@
     public static final int TYPE_RECENTS = 4;
     public static final int TYPE_DOWNLOADS = 5;
     public static final int TYPE_LOCAL = 6;
-    public static final int TYPE_CLOUD = 7;
+    public static final int TYPE_MTP = 7;
+    public static final int TYPE_CLOUD = 8;
 
     public String authority;
     public String rootId;
@@ -184,6 +185,8 @@
             derivedType = TYPE_AUDIO;
         } else if (isRecents()) {
             derivedType = TYPE_RECENTS;
+        } else if (isMtp()) {
+            derivedType = TYPE_MTP;
         } else {
             derivedType = TYPE_CLOUD;
         }
@@ -216,6 +219,15 @@
                 && "audio_root".equals(rootId);
     }
 
+    public boolean isMtp() {
+        return "com.android.mtp.documents".equals(authority);
+    }
+
+    public boolean isLibrary() {
+        return derivedType == TYPE_IMAGES || derivedType == TYPE_VIDEO || derivedType == TYPE_AUDIO
+                || derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS;
+    }
+
     @Override
     public String toString() {
         return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
deleted file mode 100644
index 8baadba..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_PACKAGE_NAME := TestDocumentsProvider
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-#LOCAL_SDK_VERSION := current
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
deleted file mode 100644
index 66988a1..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.documentsui.testing">
-    <application>
-        <provider android:name="TestDocumentsProvider"
-                android:authorities="com.android.documentsui.testing"
-                android:exported="true"
-                android:grantUriPermissions="true"
-                android:permission="android.permission.MANAGE_DOCUMENTS">
-            <intent-filter>
-                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
-            </intent-filter>
-        </provider>
-    </application>
-</manifest>
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
deleted file mode 100644
index 63ff0de5..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui.testing;
-
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MatrixCursor.RowBuilder;
-import android.os.AsyncTask;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.Document;
-import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsProvider;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class TestDocumentsProvider extends DocumentsProvider {
-    private static final String TAG = "TestDocumentsProvider";
-
-    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
-            Root.COLUMN_ROOT_ID,
-            Root.COLUMN_FLAGS,
-            Root.COLUMN_ICON,
-            Root.COLUMN_TITLE,
-            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
-    };
-
-    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
-            Document.COLUMN_DOCUMENT_ID,
-            Document.COLUMN_MIME_TYPE,
-            Document.COLUMN_DISPLAY_NAME,
-            Document.COLUMN_LAST_MODIFIED,
-            Document.COLUMN_FLAGS,
-            Document.COLUMN_SIZE,
-    };
-
-    private static String[] resolveRootProjection(String[] projection) {
-        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
-    }
-
-    private static String[] resolveDocumentProjection(String[] projection) {
-        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
-    }
-
-    @Override
-    public boolean onCreate() {
-        resetRoots();
-        return true;
-    }
-
-    @Override
-    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
-
-        RowBuilder row = result.newRow();
-        row.add(Root.COLUMN_ROOT_ID, "local");
-        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
-        row.add(Root.COLUMN_TITLE, "TEST-Local");
-        row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary");
-        row.add(Root.COLUMN_DOCUMENT_ID, "doc:local");
-
-        row = result.newRow();
-        row.add(Root.COLUMN_ROOT_ID, "create");
-        row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
-        row.add(Root.COLUMN_TITLE, "TEST-Create");
-        row.add(Root.COLUMN_DOCUMENT_ID, "doc:create");
-
-        return result;
-    }
-
-    private Map<String, Doc> mDocs = new HashMap<>();
-
-    private Doc mLocalRoot;
-    private Doc mCreateRoot;
-
-    private Doc buildDoc(String docId, String displayName, String mimeType) {
-        final Doc doc = new Doc();
-        doc.docId = docId;
-        doc.displayName = displayName;
-        doc.mimeType = mimeType;
-        mDocs.put(doc.docId, doc);
-        return doc;
-    }
-
-    public void resetRoots() {
-        Log.d(TAG, "resetRoots()");
-
-        mDocs.clear();
-
-        mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR);
-
-        mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR);
-        mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
-
-        {
-            Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1");
-            file1.contents = "fileone".getBytes();
-            file1.flags = Document.FLAG_SUPPORTS_WRITE;
-            mLocalRoot.children.add(file1);
-            mCreateRoot.children.add(file1);
-        }
-
-        {
-            Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2");
-            file2.contents = "filetwo".getBytes();
-            file2.flags = Document.FLAG_SUPPORTS_WRITE;
-            mLocalRoot.children.add(file2);
-            mCreateRoot.children.add(file2);
-        }
-
-        Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR);
-        mLocalRoot.children.add(dir1);
-
-        {
-            Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3");
-            file3.contents = "filethree".getBytes();
-            file3.flags = Document.FLAG_SUPPORTS_WRITE;
-            dir1.children.add(file3);
-        }
-
-        Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR);
-        mCreateRoot.children.add(dir2);
-
-        {
-            Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4");
-            file4.contents = "filefour".getBytes();
-            file4.flags = Document.FLAG_SUPPORTS_WRITE;
-            dir2.children.add(file4);
-        }
-    }
-
-    private static class Doc {
-        public String docId;
-        public int flags;
-        public String displayName;
-        public long size;
-        public String mimeType;
-        public long lastModified;
-        public byte[] contents;
-        public List<Doc> children = new ArrayList<>();
-
-        public void include(MatrixCursor result) {
-            final RowBuilder row = result.newRow();
-            row.add(Document.COLUMN_DOCUMENT_ID, docId);
-            row.add(Document.COLUMN_DISPLAY_NAME, displayName);
-            row.add(Document.COLUMN_SIZE, size);
-            row.add(Document.COLUMN_MIME_TYPE, mimeType);
-            row.add(Document.COLUMN_FLAGS, flags);
-            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
-        }
-    }
-
-    @Override
-    public boolean isChildDocument(String parentDocumentId, String documentId) {
-        for (Doc doc : mDocs.get(parentDocumentId).children) {
-            if (doc.docId.equals(documentId)) {
-                return true;
-            }
-            if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
-                return isChildDocument(doc.docId, documentId);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public String createDocument(String parentDocumentId, String mimeType, String displayName)
-            throws FileNotFoundException {
-        final String docId = "doc:" + System.currentTimeMillis();
-        final Doc doc = buildDoc(docId, displayName, mimeType);
-        doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME;
-        mDocs.get(parentDocumentId).children.add(doc);
-        return docId;
-    }
-
-    @Override
-    public String renameDocument(String documentId, String displayName)
-            throws FileNotFoundException {
-        mDocs.get(documentId).displayName = displayName;
-        return null;
-    }
-
-    @Override
-    public void deleteDocument(String documentId) throws FileNotFoundException {
-        mDocs.remove(documentId);
-        for (Doc doc : mDocs.values()) {
-            doc.children.remove(documentId);
-        }
-    }
-
-    @Override
-    public Cursor queryDocument(String documentId, String[] projection)
-            throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
-        mDocs.get(documentId).include(result);
-        return result;
-    }
-
-    @Override
-    public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
-            String sortOrder) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
-        for (Doc doc : mDocs.get(parentDocumentId).children) {
-            doc.include(result);
-        }
-        return result;
-    }
-
-    @Override
-    public ParcelFileDescriptor openDocument(String documentId, String mode,
-            CancellationSignal signal) throws FileNotFoundException {
-        final Doc doc = mDocs.get(documentId);
-        if (doc == null) {
-            throw new FileNotFoundException();
-        }
-        final ParcelFileDescriptor[] pipe;
-        try {
-            pipe = ParcelFileDescriptor.createPipe();
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-        if (mode.contains("w")) {
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    synchronized (doc) {
-                        try {
-                            final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
-                                    pipe[0]);
-                            doc.contents = readFullyNoClose(is);
-                            is.close();
-                            doc.notifyAll();
-                        } catch (IOException e) {
-                            Log.w(TAG, "Failed to stream", e);
-                        }
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
-            return pipe[1];
-        } else {
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    synchronized (doc) {
-                        try {
-                            final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(
-                                    pipe[1]);
-                            while (doc.contents == null) {
-                                doc.wait();
-                            }
-                            os.write(doc.contents);
-                            os.close();
-                        } catch (IOException e) {
-                            Log.w(TAG, "Failed to stream", e);
-                        } catch (InterruptedException e) {
-                            Log.w(TAG, "Interuppted", e);
-                        }
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
-            return pipe[0];
-        }
-    }
-
-    private static byte[] readFullyNoClose(InputStream in) throws IOException {
-        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        int count;
-        while ((count = in.read(buffer)) != -1) {
-            bytes.write(buffer, 0, count);
-        }
-        return bytes.toByteArray();
-    }
-}
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index cf486b1..2a540d4 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -17,4 +17,3 @@
 
 include $(BUILD_PACKAGE)
 
-include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
index efabbbf..3186add 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"እንግሊዘኛ (ዩ. ኤስ.)፣ አለም አቀፍ ቅጥ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"እንግሊዘኛ (ዩ. ኤስ.)፣ የኮልማርክ ቅጥ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"እንግሊዘኛ (ዩ. ኤስ.)፣ የድቮራክ ቅጥ"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"እንግሊዝኛ (አሜሪካ)፣ የሥራሰው ቅጥ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ጀርመን"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ፈረንሳይኛ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ፈረንሳይኛ (ካናዳ)"</string>
diff --git a/packages/InputDevices/res/values-bn-rBD/strings.xml b/packages/InputDevices/res/values-bn-rBD/strings.xml
index bfbf3d3..a0ce313 100644
--- a/packages/InputDevices/res/values-bn-rBD/strings.xml
+++ b/packages/InputDevices/res/values-bn-rBD/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ইংরেজি (US), আন্তর্জাতিক শৈলী"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ইংরেজি (US), কোলেম্যাক শৈলী"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ইংরেজি (US), ডিভোরাক শৈলী"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ইংরেজি (US), ওয়ার্কম্যান শৈলী"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"জার্মান"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ফরাসী"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ফরাসী (কানাডা)"</string>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
index 614ba56..3e7b9c8 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), mezinárodní"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), styl Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), styl Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"anglické (USA), styl Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"německé"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francouzské"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francouzské (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
index ce25623..6af0030 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Englisch (USA), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Englisch (USA), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Englisch (USA), Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Englisch (USA), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Deutsch"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Französisch"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Französisch (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml
index 0a99dbb..da6dca2 100644
--- a/packages/InputDevices/res/values-el/strings.xml
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Αγγλικά (ΗΠΑ), τύπου International"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Αγγλικά (ΗΠΑ), τύπου Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Αγγλικά (ΗΠΑ), τύπου Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Αγγλικά (ΗΠΑ), στυλ Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Γερμανικά"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Γαλλικά"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Γαλλικά (Καναδά)"</string>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 20d677b..6acf07b 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE. UU.), internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE. UU.), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE. UU.), Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EE. UU.), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
index a05d071..f2848a9 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگلیسی (ایالات متحده)، سبک بین‌المللی"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏انگلیسی (ایالات متحده)، سبک Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏انگلیسی (ایالات متحده)، سبک Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"انگلیسی (ایالات متحده)، سبک ورک‌من"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"آلمانی"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسوی"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسوی (کانادا)‏"</string>
diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml
index 250333c..284efc8 100644
--- a/packages/InputDevices/res/values-fi/strings.xml
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"englanti (Yhdysvallat), kansainvälinen"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"englanti (Yhdysvallat), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"englanti (Yhdysvallat), Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"englanti (Yhdysvallat), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"saksa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ranska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ranska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index 3842584..b26a0ea 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglais (États-Unis), style Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml
index 679dba9..a428a23 100644
--- a/packages/InputDevices/res/values-fr/strings.xml
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglais (États-Unis), style Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-gl-rES/strings.xml b/packages/InputDevices/res/values-gl-rES/strings.xml
index ab76a14..bb0f2a0 100644
--- a/packages/InputDevices/res/values-gl-rES/strings.xml
+++ b/packages/InputDevices/res/values-gl-rES/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EUA), estilo Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EUA), estilo Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-gu-rIN/strings.xml b/packages/InputDevices/res/values-gu-rIN/strings.xml
index 63c3874..e83b0ca 100644
--- a/packages/InputDevices/res/values-gu-rIN/strings.xml
+++ b/packages/InputDevices/res/values-gu-rIN/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"અંગ્રેજી (યુએસ), આંતરરાષ્ટ્રીય શૈલી"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"અંગ્રેજી (યુએસ), કોલેમેક શૈલી"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"અંગ્રેજી (યુએસ), ડ્વોરક શૈલી"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"અંગ્રેજી (યુએસ), વર્કમૅન શૈલી"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"જર્મન"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ફ્રેન્ચ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ફ્રેન્ચ (કેનેડા)"</string>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
index 829f817..27066ad 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleska (SAD), međunarodna"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleska (SAD), Colemakov raspored"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleska (SAD), Dvorakov raspored"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engleska (SAD), raspored Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemačka"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francuska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francuska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-hy-rAM/strings.xml b/packages/InputDevices/res/values-hy-rAM/strings.xml
index f47aba2..0d11645 100644
--- a/packages/InputDevices/res/values-hy-rAM/strings.xml
+++ b/packages/InputDevices/res/values-hy-rAM/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Անգլերեն (ԱՄՆ), միջազգային տեսակ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Անգլերեն (ԱՄՆ), Colemak տեսակ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Անգլերեն (ԱՄՆ), Dvorak տեսակ"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Անգլերեն (ԱՄՆ), Workman տեսակ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Գերմաներեն"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Ֆրանսերեն"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Ֆրանսերեն (Կանադա)"</string>
diff --git a/packages/InputDevices/res/values-is-rIS/strings.xml b/packages/InputDevices/res/values-is-rIS/strings.xml
index 35a5a17..de91275 100644
--- a/packages/InputDevices/res/values-is-rIS/strings.xml
+++ b/packages/InputDevices/res/values-is-rIS/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Enskt (Bandaríkin), alþjóðlegt"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Enskt (Bandaríkin), Colemak-stíll"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Enskt (Bandaríkin), Dvorak-stíll"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Enska (Bandaríkin), Workman-stíll"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Þýskt"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
index 0c8b4e95..b3bd576 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"אנגלית (ארה\"ב), סגנון בינ\"ל"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏אנגלית (ארה\"ב), סגנון Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏אנגלית (ארה\"ב), סגנון Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"‏אנגלית (ארה\"ב), סגנון Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"גרמנית"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"צרפתית"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"צרפתית (קנדה)"</string>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
index 7fbddd7..2b3daf5 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英語(アメリカ)、インターナショナル配列"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英語(アメリカ)、Colemak配列"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英語(アメリカ)、Dvorak配列"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英語(アメリカ)、Workman配列"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ドイツ語"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"フランス語"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"フランス語(カナダ)"</string>
diff --git a/packages/InputDevices/res/values-ka-rGE/strings.xml b/packages/InputDevices/res/values-ka-rGE/strings.xml
index 56bd3e3..66d147e 100644
--- a/packages/InputDevices/res/values-ka-rGE/strings.xml
+++ b/packages/InputDevices/res/values-ka-rGE/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ინგლისური (აშშ), საერთაშორისო სტილი"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ინგლისური (აშშ), Colemak სტილი"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ინგლისური (აშშ), Dvorak სტილი"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ინგლისური (აშშ), Workman სტილი"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"გერმანული"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ფრანგული"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ფრანგული (კანადა)"</string>
diff --git a/packages/InputDevices/res/values-kk-rKZ/strings.xml b/packages/InputDevices/res/values-kk-rKZ/strings.xml
index df2da88..d253542 100644
--- a/packages/InputDevices/res/values-kk-rKZ/strings.xml
+++ b/packages/InputDevices/res/values-kk-rKZ/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ағылшын (АҚШ), Халықаралық стилі"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ағылшын (АҚШ), Colemak стилі"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ағылшын (АҚШ), Dvorak стилі"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ағылшын (АҚШ), Workman стилі"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Неміс"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-kn-rIN/strings.xml b/packages/InputDevices/res/values-kn-rIN/strings.xml
index f88076c..243e6597 100644
--- a/packages/InputDevices/res/values-kn-rIN/strings.xml
+++ b/packages/InputDevices/res/values-kn-rIN/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ಇಂಗ್ಲಿಷ್ (US), ಅಂತರರಾಷ್ಟ್ರೀಯ ಶೈಲಿ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ಇಂಗ್ಲಿಷ್ (US), ಕೋಲ್ಮಾರ್ಕ್ ಶೈಲಿ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ಇಂಗ್ಲಿಷ್ (US), ಡಿವೊರಾಕ್ ಶೈಲಿ"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ಇಂಗ್ಲೀಷ್ (US), ವರ್ಕ್‌ಮನ್ ಶೈಲಿ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ಜರ್ಮನ್"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ಫ್ರೆಂಚ್"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ಫ್ರೆಂಚ್‌ (ಕೆನಡಾ)"</string>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index d215375..7758210 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"영어(미국), 글로벌 스타일"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"영어(미국), 콜맥 스타일"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"영어(미국), 드보락 스타일"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"영어(미국), 워크맨 스타일"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"독일어"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"프랑스어"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"프랑스어(캐나다)"</string>
diff --git a/packages/InputDevices/res/values-ky-rKG/strings.xml b/packages/InputDevices/res/values-ky-rKG/strings.xml
index ee9cc5a..aa74733 100644
--- a/packages/InputDevices/res/values-ky-rKG/strings.xml
+++ b/packages/InputDevices/res/values-ky-rKG/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англис (АКШ), эл аралык"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англис (АКШ), Colemak стили"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англис (АКШ), Dvorak стили"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Англисче (АКШ), Workman стили"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Немис"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-lo-rLA/strings.xml b/packages/InputDevices/res/values-lo-rLA/strings.xml
index 33c9f61..05b1b83 100644
--- a/packages/InputDevices/res/values-lo-rLA/strings.xml
+++ b/packages/InputDevices/res/values-lo-rLA/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ອັງກິດ (ສະຫະລັດຯ), ແບບສາກົນ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ອັງກິດ (ສະຫະລັດ), ແບບຄົນ​ເຮັດ​ວຽກ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ເຢຍລະມັນ"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ຝຣັ່ງ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ຝຣັ່ງ (ຄານາດາ)"</string>
diff --git a/packages/InputDevices/res/values-ml-rIN/strings.xml b/packages/InputDevices/res/values-ml-rIN/strings.xml
index b42bdb7..0faa40e 100644
--- a/packages/InputDevices/res/values-ml-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ml-rIN/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ഇംഗ്ലീഷ് (യു.എസ്), അന്തർദ്ദേശീയ ശൈലി"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ഇംഗ്ലീഷ് (യു.എസ്), കോൽമാക് ശൈലി"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ഇംഗ്ലീഷ് (യു.എസ്), ദ്വരോക്ക് ശൈലി"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ഇംഗ്ലീഷ് (യുഎസ്), വർക്ക്‌മാൻ ശൈലി"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ജര്‍‌മ്മന്‍‌"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ഫ്രഞ്ച്"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ഫ്രഞ്ച് (കാനഡ)"</string>
diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml
index 378cdc1..37604e3 100644
--- a/packages/InputDevices/res/values-nb/strings.xml
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), internasjonal stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelsk (USA), workman-stil"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ne-rNP/strings.xml b/packages/InputDevices/res/values-ne-rNP/strings.xml
index 571d717..4c3dec3 100644
--- a/packages/InputDevices/res/values-ne-rNP/strings.xml
+++ b/packages/InputDevices/res/values-ne-rNP/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अङ्ग्रेजी (अमेरिकी), अन्तर्राष्ट्रिय शैली"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अङ्ग्रेजी (अमेरिकी), कोलमाक शैली"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अङ्ग्रेजी (अमेरिकी), डेभोर्याक शैली"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"अंग्रेजी (अमेरिका), वर्कम्यान शैली"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ्रान्सेली"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ्रेंच (क्यानाडा)"</string>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index 7175705..2ae815e32 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angielski (USA), międzynarodowy"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angielski (USA), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angielski (USA), Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Angielski (USA), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Niemiecki"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francuski"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francuski (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml
index 15c2d37..a1503a4 100644
--- a/packages/InputDevices/res/values-pt-rBR/strings.xml
+++ b/packages/InputDevices/res/values-pt-rBR/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
index 15c2d37..a1503a4 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml
index ccfe568..795e9a2 100644
--- a/packages/InputDevices/res/values-ro/strings.xml
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engleză (SUA), stil internațional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engleză (SUA), stil Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engleză (SUA), stil Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engleză (SUA), stil Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Germană"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franceză"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franceză (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml
index 7d3038e..ac4c81b 100644
--- a/packages/InputDevices/res/values-ru/strings.xml
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"английский (США, международная)"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английский (США, Colemak)"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английский (США, Dvorak)"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"английский (США, Workman)"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немецкий"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"французский"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французский (Канада)"</string>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
index f9e4610..a643c8e 100644
--- a/packages/InputDevices/res/values-sl/strings.xml
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angleška (ZDA), mednarodni slog"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angleška (ZDA), slog Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angleška (ZDA), slog Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"angleška (ZDA), slog Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemška"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francoska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francoska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml
index a216bc3..b06f6fc 100644
--- a/packages/InputDevices/res/values-sr/strings.xml
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"енглеска (САД), међународни стил"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"енглеска (САД), Colemak стил"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"енглеска (САД), Dvorak стил"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"енглеска (САД), Workman стил"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немачка"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"француска"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"француска (Канада)"</string>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index 23f2c98..89cb54e 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelskt (USA), workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
index 720d427..1f447b0 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Kiingereza (Marekani), Muundo wa Kimataifa"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Kiingereza (Marekani), Muundo wa Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Kiingereza (Marekani), Muundo wa Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Kiingereza (US), mtindo wa Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Kijerumani"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Kifaransa"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Kifaransa (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-ta-rIN/strings.xml b/packages/InputDevices/res/values-ta-rIN/strings.xml
index 43d0bd18..32efe7b 100644
--- a/packages/InputDevices/res/values-ta-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ta-rIN/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ஆங்கிலம் (யூஎஸ்), சர்வதேச நடை"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ஆங்கிலம் (யூஎஸ்), கோல்மாக் நடை"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ஆங்கிலம் (யூஎஸ்), டிவாரக் நடை"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ஆங்கிலம் (யூஎஸ்), வொர்க்மென் நடை"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ஜெர்மன்"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ஃபிரெஞ்ச்"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ஃபிரெஞ்ச் (கனடா)"</string>
diff --git a/packages/InputDevices/res/values-te-rIN/strings.xml b/packages/InputDevices/res/values-te-rIN/strings.xml
index d214ae6..e07d4c8 100644
--- a/packages/InputDevices/res/values-te-rIN/strings.xml
+++ b/packages/InputDevices/res/values-te-rIN/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ఇంగ్లీష్ (US), అంతర్జాతీయ శైలి"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ఇంగ్లీష్ (US), కొల్‌మాక్ శైలి"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ఇంగ్లీష్ (US), ద్వోరక్ శైలి"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ఆంగ్లం (US), వర్క్‌మాన్ శైలి"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"జర్మన్"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ఫ్రెంచ్"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ఫ్రెంచ్ (కెనడా)"</string>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
index 3dd0d3b..a8d9a0f 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilizce (ABD) Uluslararası stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilizce (ABD) Colemak stili"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilizce (ABD) Dvorak stili"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"İngilizce (ABD), Workman stili"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Almanca"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransızca"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransızca (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-ur-rPK/strings.xml b/packages/InputDevices/res/values-ur-rPK/strings.xml
index 2c5a8b8..3d2f618 100644
--- a/packages/InputDevices/res/values-ur-rPK/strings.xml
+++ b/packages/InputDevices/res/values-ur-rPK/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگریزی (امریکہ)، انٹرنیشنل سٹائل"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگریزی (امریکہ)، کول مارک سٹائل"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگریزی (امریکہ)، ڈوراک سٹائل"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"‏انگریزی (US)، ورک مین اسٹائل"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"جرمن"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسیسی"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسیسی (کینیڈا)"</string>
diff --git a/packages/InputDevices/res/values-uz-rUZ/strings.xml b/packages/InputDevices/res/values-uz-rUZ/strings.xml
index ee20328..9c55615 100644
--- a/packages/InputDevices/res/values-uz-rUZ/strings.xml
+++ b/packages/InputDevices/res/values-uz-rUZ/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglizcha (AQSH), xalqaro uslubda"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglizcha (AQSH), Kolemak uslubida"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglizcha (AQSH), Dvorak uslubida"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ingliz (AQSH), ishchi uslubda"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Nemischa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransuzcha"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransuzcha (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml
index 2ba2667..c61dccb 100644
--- a/packages/InputDevices/res/values-zh-rCN/strings.xml
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英语(美国),国际风格"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英语(美国),Colemak 风格"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英语(美国),Dvorak 风格"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英语(美国),Workman 风格"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德语"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"法语"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法语(加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml
index 80899d0..0dcffb0 100644
--- a/packages/InputDevices/res/values-zu/strings.xml
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -8,8 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"I-English (US), isitayela sakwamanye amazwe"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"I-English (US), isitayela se-Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"I-English (US), isitayela se-Dvorak"</string>
-    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
-    <skip />
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"I-English (US), isitayela sokusebenza"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Isi-German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Isi-French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Isi-French (Canada)"</string>
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 11e937b..df4a9f0 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -2,6 +2,7 @@
 
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
@@ -19,8 +20,15 @@
  * Database for MTP objects.
  * The object handle which is identifier for object in MTP protocol is not stable over sessions.
  * When we resume the process, we need to remap our document ID with MTP's object handle.
- * The database object remembers the map of document ID and fullpath, and helps to remap object
- * handle and document ID by comparing fullpath.
+ *
+ * If the remote MTP device is backed by typical file system, the file name
+ * is unique among files in a directory. However, MTP protocol itself does
+ * not guarantee the uniqueness of name so we cannot use fullpath as ID.
+ *
+ * Instead of fullpath, we use artificial ID generated by MtpDatabase itself. The database object
+ * remembers the map of document ID and object handle, and remaps new object handle with document ID
+ * by comparing the directory structure and object name.
+ *
  * TODO: Remove @VisibleForTesting annotation when we start to use this class.
  */
 @VisibleForTesting
@@ -30,10 +38,9 @@
 
     private static final String TABLE_MTP_DOCUMENTS = "MtpDocuments";
 
-    static final String COLUMN_DEVICE_ID = "deviceId";
-    static final String COLUMN_STORAGE_ID = "storageId";
-    static final String COLUMN_OBJECT_HANDLE = "objectHandle";
-    static final String COLUMN_FULL_PATH = "fullPath";
+    static final String COLUMN_DEVICE_ID = "device_id";
+    static final String COLUMN_STORAGE_ID = "storage_id";
+    static final String COLUMN_OBJECT_HANDLE = "object_handle";
 
     private static class OpenHelper extends SQLiteOpenHelper {
         private static final String CREATE_TABLE_QUERY =
@@ -43,7 +50,6 @@
                 COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
                 COLUMN_STORAGE_ID + " INTEGER NOT NULL," +
                 COLUMN_OBJECT_HANDLE + " INTEGER," +
-                COLUMN_FULL_PATH + " TEXT NOT NULL," +
                 DocumentsContract.Document.COLUMN_MIME_TYPE + " TEXT," +
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
                 DocumentsContract.Document.COLUMN_SUMMARY + " TEXT," +
@@ -86,17 +92,15 @@
     }
 
     @VisibleForTesting
-    void putRootDocument(MtpRoot root) throws Exception {
+    void putRootDocument(Resources resources, MtpRoot root) throws Exception {
         database.beginTransaction();
         try {
             final ContentValues values = new ContentValues();
             values.put(COLUMN_DEVICE_ID, root.mDeviceId);
             values.put(COLUMN_STORAGE_ID, root.mStorageId);
             values.putNull(COLUMN_OBJECT_HANDLE);
-            values.put(
-                    COLUMN_FULL_PATH, "/" + root.mDeviceId + "/" + escape(root.mDescription));
             values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
-            values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription);
+            values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
             values.putNull(Document.COLUMN_SUMMARY);
             values.putNull(Document.COLUMN_LAST_MODIFIED);
             values.putNull(Document.COLUMN_ICON);
@@ -113,7 +117,7 @@
     }
 
     @VisibleForTesting
-    void putDocument(int deviceId, String parentFullPath, MtpObjectInfo info) throws Exception {
+    void putDocument(int deviceId, MtpObjectInfo info) throws Exception {
         database.beginTransaction();
         try {
             final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
@@ -134,9 +138,7 @@
             values.put(COLUMN_DEVICE_ID, deviceId);
             values.put(COLUMN_STORAGE_ID, info.getStorageId());
             values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
-            values.put(COLUMN_FULL_PATH, parentFullPath + "/" + escape(info.getName()));
-            values.put(
-                    Document.COLUMN_MIME_TYPE, CursorHelper.formatTypeToMimeType(info.getFormat()));
+            values.put(Document.COLUMN_MIME_TYPE, mimeType);
             values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
             values.putNull(Document.COLUMN_SUMMARY);
             values.put(
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 7ce32542..8e1335fb 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -1,6 +1,5 @@
 package com.android.mtp;
 
-
 import android.database.Cursor;
 import android.mtp.MtpConstants;
 import android.mtp.MtpObjectInfo;
@@ -15,7 +14,6 @@
         MtpDatabase.COLUMN_DEVICE_ID,
         MtpDatabase.COLUMN_STORAGE_ID,
         MtpDatabase.COLUMN_OBJECT_HANDLE,
-        MtpDatabase.COLUMN_FULL_PATH,
         DocumentsContract.Document.COLUMN_MIME_TYPE,
         DocumentsContract.Document.COLUMN_DISPLAY_NAME,
         DocumentsContract.Document.COLUMN_SUMMARY,
@@ -25,6 +23,8 @@
         DocumentsContract.Document.COLUMN_SIZE
     };
 
+    private final TestResources resources = new TestResources();
+
     @Override
     public void tearDown() {
         MtpDatabase.deleteDatabase(getContext());
@@ -32,35 +32,10 @@
 
     public void testPutRootDocument() throws Exception {
         final MtpDatabase database = new MtpDatabase(getContext());
-        final MtpRoot root = new MtpRoot(
-                0,
-                1,
-                "Device A",
-                "Storage",
-                1000,
-                2000,
-                "");
-        database.putRootDocument(root);
-
-        final MtpRoot duplicatedNameRoot = new MtpRoot(
-                0,
-                2,
-                "Device A",
-                "Storage",
-                1000,
-                2000,
-                "");
-        database.putRootDocument(duplicatedNameRoot);
-
-        final MtpRoot strangeNameRoot = new MtpRoot(
-                0,
-                3,
-                "Device A",
-                "/@#%&<>Storage",
-                1000,
-                2000,
-                "");
-        database.putRootDocument(strangeNameRoot);
+        database.putRootDocument(resources, new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""));
+        database.putRootDocument(resources, new MtpRoot(0, 2, "Device", "Storage", 1000, 2000, ""));
+        database.putRootDocument(
+                resources, new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 1000, 2000, ""));
 
         final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
         assertEquals(3, cursor.getCount());
@@ -70,22 +45,23 @@
         assertEquals("deviceId", 0, cursor.getInt(1));
         assertEquals("storageId", 1, cursor.getInt(2));
         assertTrue("objectHandle", cursor.isNull(3));
-        assertEquals("fullPath", "/0/Storage", cursor.getString(4));
-        assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(5));
-        assertEquals("displayName", "Storage", cursor.getString(6));
-        assertTrue("summary", cursor.isNull(7));
-        assertTrue("lastModified", cursor.isNull(8));
-        assertTrue("icon", cursor.isNull(9));
-        assertEquals("flag", 0, cursor.getInt(10));
-        assertEquals("size", 1000, cursor.getInt(11));
+        assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(4));
+        assertEquals("displayName", "Device Storage", cursor.getString(5));
+        assertTrue("summary", cursor.isNull(6));
+        assertTrue("lastModified", cursor.isNull(7));
+        assertTrue("icon", cursor.isNull(8));
+        assertEquals("flag", 0, cursor.getInt(9));
+        assertEquals("size", 1000, cursor.getInt(10));
 
         cursor.moveToNext();
         assertEquals("documentId", 2, cursor.getInt(0));
-        assertEquals("fullPath", "/0/Storage", cursor.getString(4));
+        assertEquals("displayName", "Device Storage", cursor.getString(5));
 
         cursor.moveToNext();
         assertEquals("documentId", 3, cursor.getInt(0));
-        assertEquals("fullPath", "/0/%2F%40%23%25%26%3C%3EStorage", cursor.getString(4));
+        assertEquals("displayName", "Device /@#%&<>Storage", cursor.getString(5));
+
+        cursor.close();
     }
 
     public void testPutDocument() throws Exception {
@@ -96,7 +72,7 @@
         builder.setStorageId(5);
         builder.setFormat(MtpConstants.FORMAT_TEXT);
         builder.setCompressedSize(1000);
-        database.putDocument(0, "/0/Storage", builder.build());
+        database.putDocument(0, builder.build());
 
         final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
         assertEquals(1, cursor.getCount());
@@ -105,17 +81,16 @@
         assertEquals("deviceId", 0, cursor.getInt(1));
         assertEquals("storageId", 5, cursor.getInt(2));
         assertEquals("objectHandle", 100, cursor.getInt(3));
-        assertEquals("fullPath", "/0/Storage/test.txt", cursor.getString(4));
-        assertEquals("mimeType", "text/plain", cursor.getString(5));
-        assertEquals("displayName", "test.txt", cursor.getString(6));
-        assertTrue("summary", cursor.isNull(7));
-        assertTrue("lastModified", cursor.isNull(8));
-        assertTrue("icon", cursor.isNull(9));
+        assertEquals("mimeType", "text/plain", cursor.getString(4));
+        assertEquals("displayName", "test.txt", cursor.getString(5));
+        assertTrue("summary", cursor.isNull(6));
+        assertTrue("lastModified", cursor.isNull(7));
+        assertTrue("icon", cursor.isNull(8));
         assertEquals(
                 "flag",
                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
-                cursor.getInt(10));
-        assertEquals("size", 1000, cursor.getInt(11));
+                cursor.getInt(9));
+        assertEquals("size", 1000, cursor.getInt(10));
     }
 }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 0c03814c..5765f0a 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -16,9 +16,6 @@
 
 package com.android.mtp;
 
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
 import android.mtp.MtpConstants;
 import android.mtp.MtpObjectInfo;
@@ -26,7 +23,6 @@
 import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsContract;
 import android.test.AndroidTestCase;
-import android.test.mock.MockResources;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import java.io.FileNotFoundException;
@@ -39,16 +35,7 @@
     private TestContentResolver mResolver;
     private MtpDocumentsProvider mProvider;
     private TestMtpManager mMtpManager;
-    private final MockResources mResources = new MockResources() {
-        @Override
-        public String getString(int id) throws NotFoundException {
-            switch (id) {
-                case R.string.root_name:
-                    return "%1$s %2$s";
-            }
-            throw new NotFoundException();
-        }
-    };
+    private final TestResources mResources = new TestResources();
 
     @Override
     public void setUp() throws IOException {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
new file mode 100644
index 0000000..eb80e3b
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mtp;
+
+import android.test.mock.MockResources;
+
+class TestResources extends MockResources {
+    @Override
+    public String getString(int id) throws NotFoundException {
+        switch (id) {
+            case R.string.root_name:
+                return "%1$s %2$s";
+        }
+        throw new NotFoundException();
+    }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5d622a0..be5c0fe 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -214,6 +214,7 @@
                   android:resumeWhilePausing="true"
                   android:screenOrientation="behind"
                   android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize"
                   android:theme="@style/RecentsTheme.Wallpaper">
             <intent-filter>
                 <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 6187070..cc35c51c 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -35,18 +35,22 @@
     <com.android.systemui.qs.QuickQSPanel
         android:id="@+id/quick_qs_panel"
         android:background="#0000"
-        android:layout_width="142dp"
+        android:layout_width="144dp"
         android:layout_height="match_parent"
-        android:layout_alignParentEnd="true" />
+        android:layout_alignParentEnd="true"
+        android:layout_marginEnd="12dp" />
 
     <LinearLayout
         android:id="@+id/expanded_group"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
+        android:gravity="center"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:orientation="horizontal"
-        android:layout_alignParentEnd="true">
+        android:layout_alignParentEnd="true"
+        android:layout_marginEnd="10dp">
+
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
             android:id="@+id/settings_button_container"
             android:layout_width="48dp"
@@ -74,7 +78,8 @@
 
         <ImageView
             android:layout_width="48dp"
-            android:layout_height="match_parent"
+            android:layout_height="48dp"
+            android:padding="12dp"
             android:src="@drawable/ic_expand_less"
             android:tint="@android:color/white" />
     </LinearLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 41ed64c..0e31d8d 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Vou uit"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Vou in"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skerm is vasgespeld"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Dit hou dit in sig totdat jy dit ontspeld. Raak en hou Terug en Oorsig op dieselfde tyd om te ontspeld."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Dit hou dit in sig totdat jy ontspeld. Raak en hou Oorsig om te ontspeld."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Het dit"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nee, dankie"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Versteek <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 673710d..ec6ec93 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"አስፋ"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ሰብስብ"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"ማያ ገጽ ተሰክቷል"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"ይህ እስከሚነቅሉት ድረስ ድረስ በዕይታ ውስጥ እንዲቆይ ያደርገዋል። ለመንቀል በተመሳሳይ ጊዜ ተመለስን እና አጠቃላይ ዕይታን አንድ ላይ ነክተው ይያዙ።"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ይህ እስከሚነቅሉት ድረስ በዕይታ ውስጥ ያቆየዋል። እንዲነቀል ለማድረግ አጠቃላይ ዕይታን ነካ አድርገው ይያዙት።"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"ገባኝ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"አይ፣ አመሰግናለሁ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ይደበቅ?"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index c2c7e65..02fc3fe 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -397,8 +397,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"توسيع"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"تصغير"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"تم تثبيت الشاشة"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"يساعد هذا على استمرار العرض حتى يتم إلغاء التثبيت. ويمكنك لمس \"رجوع\" و\"عرض عام\" مع الاستمرار في وقت واحد لإلغاء التثبيت."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"يساعد هذا على استمرار العرض حتى يتم إلغاء التثبيت. ويمكنك لمس \"عرض عام\" مع الاستمرار في وقت واحد لإلغاء التثبيت."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"حسنًا"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"لا، شكرًا"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"هل تريد إخفاء <xliff:g id="TILE_LABEL">%1$s</xliff:g>؟"</string>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index a949dbbe8..aba22f8 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişləndirin"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yığcamlaşdırın"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekrana sancaq taxıldı"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Sancaq götürülənə qədər bu, görünəcək. Sancağı götürmək üçün Geri və İcmal düymələrinə eyni vaxtda toxunun və saxlayın."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Sancaq götürülənə qədər bu, görünəcək. Sancağı götürmək üçün Geri və İcmal düymələrinə toxunun və saxlayın."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Anladım!"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Yox, çox sağ olun"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> gizlədilsin?"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index a59f4c5..50ac61f 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Разгъване"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свиване"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Екранът е фиксиран"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Така екранът ще се показва, докато не го освободите. За да направите това, докоснете и задръжте бутона за връщане назад и този за общ преглед едновременно."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Така екранът ще се показва, докато не го освободите. За да направите това, докоснете и задръжте бутона за общ преглед."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Разбрах"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Не, благодаря"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Да се скрие ли „<xliff:g id="TILE_LABEL">%1$s</xliff:g>“?"</string>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index f4adc14..f32cfcc 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"প্রসারিত করুন"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"সঙ্কুচিত করুন"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"স্ক্রীন পিন করা হয়েছে"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"এটি আপনার আনপিন না করা পর্যন্ত এটিকে দর্শনে রাখে৷ আনপিন করতে একই সময়ে ফিরুন এবং ওভারভিউ এ স্পর্শ করে ধরে রাখুন৷"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"এটি আপনার আনপিন না করা পর্যন্ত এটিকে দর্শনে রাখে৷ আনপিন করতে ওভারভিউ এ স্পর্শ করে ধরে রাখুন৷"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"বুঝেছি"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"না থাক"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> লুকাবেন?"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 00f96a7..429f70b 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Amplia"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Replega"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"La pantalla està fixada"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Continuarà a la visualització fins que n\'anul·lis la fixació. Per fer-ho, toca i mantén premuts els botons Enrere i Visió general a la vegada."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Continuarà a la visualització fins que n\'anul·lis la fixació. Per fer-ho, toca i mantén premut el botó Visió general."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"D\'acord"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gràcies"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vols amagar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index e429a06..ee77cf9 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -397,8 +397,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbalit"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sbalit"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je připnuta"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Chcete-li jej uvolnit, stiskněte a podržte současně tlačítka Zpět a Přehled."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Uvolníte jej stisknutím a podržením tlačítka Přehled."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Rozumím"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, děkuji"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Skrýt <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index e4436c8..f749756 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Udvid"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skærmen er fastgjort"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Dette fastholder den i visningen, indtil du frigør den. Tryk på Tilbage og Oversigt på samme tid, og hold dem nede for at frigøre denne skærm."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Dette fastholder den i visningen, indtil du frigør den. Tryk på Oversigt, og hold den nede for at frigøre denne skærm."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK, det er forstået"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tak"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vil du skjule <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index b113083..cfaecaf 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Maximieren"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minimieren"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Bildschirm ist fixiert"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Der Bildschirm wird solange angezeigt, bis Sie die Fixierung aufheben. Berühren und halten Sie \"Zurück\" und \"Übersicht\" gleichzeitig, um die Fixierung aufzuheben."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Der Bildschirm wird solange angezeigt, bis Sie die Fixierung aufheben. Berühren und halten Sie \"Übersicht\", wenn Sie die Fixierung aufheben möchten."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nein danke"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ausblenden?"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 27a019c..6fc1b59 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Ανάπτυξη"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Σύμπτυξη"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Η οθόνη καρφιτσώθηκε"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Με αυτόν τον τρόπο παραμένει σε προβολή έως ότου την ξεκαρφιτσώσετε. Αγγίξτε παρατεταμένα \"Επιστροφή\" και \"Επισκόπηση\" ταυτόχρονα για ξεκαρφίτσωμα."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Με αυτόν τον τρόπο παραμένει σε προβολή έως ότου την ξεκαρφιτσώσετε. Αγγίξτε παρατεταμένα \"Επισκόπηση\" για ξεκαρφίτσωμα."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Το κατάλαβα"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Όχι"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Απόκρυψη <xliff:g id="TILE_LABEL">%1$s</xliff:g>;"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index b1f71a2..ffdc3af7 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index b1f71a2..ffdc3af7 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index b1f71a2..ffdc3af7 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index bca484a..d4bb0a6 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fija"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Esta función mantiene fija la vista de la pantalla hasta que la desactivas. Mantén presionados los botones Atrás y Recientes al mismo tiempo para anular la fijación."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Esta función mantiene fija la vista de la pantalla hasta que la desactivas. Mantén presionado el botón Recientes para anular la fijación."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"¿Ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 3c89db8..d3cae62 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Mostrar"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ocultar"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fijada"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para ello, mantén pulsados los botones de retroceso e información general."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para ello, mantén pulsado el botón de información general."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"¿Ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 01e5848..0dfbdfc 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Laiendamine"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ahendamine"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekraan on kinnitatud"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"See hoiab selle kuval, kuni selle vabastate. Vabastamiseks puudutage ning hoidke korraga all nuppe Tagasi ja Ülevaade."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"See hoiab selle kuval, kuni selle vabastate. Vabastamiseks puudutage ja hoidke all nuppu Ülevaade."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Selge"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Tänan, ei"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Kas peita <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index 613b44a..769164d 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Zabaldu"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tolestu"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Pantaila ainguratuta dago"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Horrela, ikusgai mantenduko da aingura kendu arte. Aingura kentzeko, eduki ukituta aldi berean \"Atzera\" eta \"Ikuspegi orokorra\" botoiak."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Horrela, ikusgai mantenduko da, aingura kendu arte. Aingura kentzeko, eduki ukituta \"Ikuspegi orokorra\" botoia."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Ados"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ez, eskerrik asko"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ezkutatu nahi duzu?"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 9967225..6d9d43f 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -219,7 +219,7 @@
     <string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"داده موقتاً متوقف شده است"</string>
     <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"چون به محدودیت داده تنظیم شده رسیده‌اید، دستگاه مصرف داده را برای باقیمانده این دوره موقتاً متوقف کرده است.\n\nاگر ادامه دهید شاید موجب کسر هزینه از طرف شرکت مخابراتی شما شود."</string>
     <string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"از سر‌گیری"</string>
-    <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"اتصال اینترنتی وجود ندارد"</string>
+    <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"اتصال اینترنتی ندارید"</string>
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"‏Wi-Fi متصل شد"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"‏جستجو برای GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"‏مکان تنظیم شده توسط GPS"</string>
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"بزرگ کردن"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"کوچک کردن"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"صفحه نمایش پین شد"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"تا زمانی که پین را بردارید، در نما نگه‌داشته می‌شود. برای برداشتن پین، برگشت و نمای کلی را به صورت هم‌زمان لمس کنید و نگه‌دارید."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"تا زمانی که پین را بردارید، در نما نگه‌داشته می‌شود. برای برداشتن پین، نمای کلی را لمس کنید و نگه‌دارید."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"متوجه شدم"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"نه سپاسگزارم"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> مخفی شود؟"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 2d138cb..79bf369 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Laajenna."</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tiivistä."</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Näyttö on kiinnitetty"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Tämä pitää sen näkyvissä, kunnes poistat kiinnityksen. Kosketa Edellinen- ja Viimeisimmät-kohtaa samanaikaisesti pitkään kiinnityksen poistamiseksi."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Tämä pitää sen näkyvissä, kunnes poistat kiinnityksen. Kosketa Viimeisimmät-kohtaa pitkään kiinnityksen poistamiseksi."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Selvä"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ei kiitos"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Piilotetaanko <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 979ee35..605e4c5 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"L\'écran est épinglé"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Cet écran est épinglé jusqu\'à ce que vous annuliez l\'opération. Pour annuler l\'épinglage, maintenez un doigt simultanément sur « Retour » et « Aperçu »."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Cet écran est épinglé jusqu\'à ce que vous annuliez l\'opération. Pour annuler l\'épinglage, maintenez le doigt sur « Aperçu »."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Non, merci"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Masquer <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 9ab6613..6beed78 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -327,8 +327,8 @@
     <string name="interruption_level_priority_twoline" msgid="1564715335217164124">"Priorité\nuniquement"</string>
     <string name="interruption_level_alarms_twoline" msgid="3266909566410106146">"Alarmes\nuniquement"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Charge en cours… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
-    <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Charge rapide… (chargé à 100 % dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
-    <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Charge lente… (chargé à 100 % dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Charge rapide… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Charge lente… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Changer d\'utilisateur"</string>
     <string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Changer d\'utilisateur (utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>)"</string>
     <string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Écran épinglé"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Cet écran est épinglé jusqu\'à annulation de l\'opération. Pour annuler l\'épinglage, appuyez simultanément sur \"Retour\" et \"Aperçu\" de manière prolongée."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Cet écran est épinglé jusqu\'à annulation de l\'opération. Pour annuler l\'épinglage, appuyez de manière prolongée sur \"Aperçu\"."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Non, merci"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Masquer <xliff:g id="TILE_LABEL">%1$s</xliff:g> ?"</string>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index bc44150..9a66d87 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Ampliar"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"A pantalla está fixada"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"A pantalla manterase visible ata que anules a fixación. Para facelo, mantén premido Atrás e Visión xeral ao mesmo tempo."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"A pantalla manterase visible ata que anules a fixación. Para facelo, mantén premido Visión xeral."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"De acordo"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Non, grazas"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Queres ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index ba8c517..fe60448 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"વિસ્તૃત કરો"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"સંકુચિત કરો"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"સ્ક્રીન પિન કરેલ છે"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યમાં રાખે છે. અનપિન કરવા માટે બેકને ટચ કરો અને પકડો અને તે જ સમયે વિહંગાવલોકન કરો."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યમાં રાખે છે. અનપિન કરવા માટે વિહંગાવલોકનને ટચ કરો અને પકડો."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"સમજાઈ ગયું"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"નહીં આભાર"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ને છુપાવીએ?"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 669ce5a..a433ca5 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करें"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त करें"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"स्‍क्रीन पिन कर दी गई है"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"इससे वह तब तक दृश्‍य में रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए वापस जाएं और अवलोकन करें को एक ही समय पर स्‍पर्श करके रखें."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"इससे वह तब तक दृश्‍य में बना रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए अवलोकन करें को स्‍पर्श करके रखें."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"समझ लिया"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"नहीं, रहने दें"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> को छिपाएं?"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index e928cbc..b72ac3d 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -394,8 +394,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Proširivanje"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sažimanje"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je prikvačen"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Zaslon će tako ostati u prvom planu dok ga ne otkvačite. Istovremeno dodirnite i držite Natrag i Pregled da biste ga otkvačili."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Zaslon će tako ostati u prvom planu dok ga ne otkvačite. Dodirnite i držite Pregled da biste ga otkvačili."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Shvaćam"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, hvala"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Želite li sakriti pločicu <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index a347ede..d27813a 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Kibontás"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Összecsukás"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"A képernyő rögzítve van"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Megjelenítve tartja addig, amíg Ön fel nem oldja fel a rögzítést. A rögzítés feloldásához tartsa egyszerre lenyomva a Vissza és az Áttekintés lehetőséget."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz tartsa lenyomva az Áttekintés lehetőséget."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Értem"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nem, köszönöm"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Elrejti ezt: <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 18f6c88..125b19d 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Ընդարձակել"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Կոծկել"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Էկրանն ամրացված է"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար միաժամանակ հպեք և պահեք Համատեսքի և Հետ կոճակները:"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար հպեք և պահեք Համատեսքի կոճակը:"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Հասկանալի է"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ոչ, շնորհակալություն"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Թաքցնե՞լ <xliff:g id="TILE_LABEL">%1$s</xliff:g>-ը:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index afcac63..1370ef40 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Luaskan"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ciutkan"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Layar dipasangi pin"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh dan tahan tombol Kembali dan Ringkasan secara bersamaan untuk melepas pin."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh dan tahan tombol Ringkasan untuk melepas pin."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Mengerti"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Lain kali"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Sembunyikan <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index 48d0d1a..a57e66c 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Stækka"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minnka"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skjárinn er festur"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Þetta heldur þessu opnu þangað til þú losar. Haltu bakk- og yfirlitshnöppunum inni á sama tíma til að losa."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Þetta heldur þessu opnu þangað til þú losar. Haltu yfirlitshnappinum inni til að losa."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Ég skil"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nei, takk"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Fela <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index dea0330..77be017 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Espandi"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Comprimi"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"La schermata è bloccata"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"La schermata rimane visibile finché la sblocchi. Tocca e tieni premuti contemporaneamente Indietro e Panoramica per sbloccare."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"La schermata rimane visibile finché la sblocchi. Tocca Panoramica e tieni premuto per sbloccare."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, grazie"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Nascondere <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 772a3fa..20cb1ea 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"הרחב"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"כווץ"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"המסך מוצמד"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"שומר בתצוגה עד לביטול ההצמדה. גע והחזק בו-זמנית ב\'הקודם\' ו\'סקירה\' כדי לבטל הצמדה."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"שומר בתצוגה עד לביטול ההצמדה. גע והחזק בו-זמנית ב\'הקודם\' ו\'סקירה\' כדי לבטל הצמדה."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"הבנתי"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"לא, תודה"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"להסתיר<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 6e570e3..3d40c73 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"折りたたむ"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"画面が固定されました"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"固定を解除するまで画面が常に表示されるようになります。[戻る]と[最近]を同時に押し続けると固定が解除されます。"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"固定を解除するまで画面が常に表示されるようになります。[最近]を押し続けると固定が解除されます。"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"はい"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"いいえ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>を非表示にしますか?"</string>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index 6f26123..8f42d5b 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"გავრცობა"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ჩაკეცვა"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"ეკრანი ჩამაგრებულია"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"ამით ის ხედში დარჩება, სანამ ჩამაგრებას არ გააუქმებთ. ჩამაგრების გასაუქმებლად შეეხეთ და დააყოვნეთ „დაბრუნება“-ზე და „მიმოხილვა“-ზე ერთდროულად."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ამით ის ხედში დარჩება, სანამ ჩამაგრებას არ გააუქმებთ. ჩამაგრების გასაუქმებლად შეეხეთ და დააყოვნეთ „მიმოხილვა“-ზე."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"გასაგებია"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"არა, გმადლობთ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"დაიმალოს <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 2668de4..192518a 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Жаю"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жию"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Экран түйрелді"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Бұл сіз оны босатқанша оны көрсетіп тұрады. Босату үшін «Кері» және «Шолу» түймелерін бір уақытта басып тұрыңыз."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Бұл сіз оны босатқанша оны көрсетіп тұрады. Босату үшін «Шолу» түймесін бір уақытта басып тұрыңыз."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Түсіндім"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Жоқ, рақмет"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> жасыру керек пе?"</string>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 08d761f..284af53 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"ពង្រីក"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"បង្រួម"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"អេក្រង់​ត្រូវ​បាន​ភ្ជាប់"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"រក្សាទុក​វា​ក្នុង​ទិដ្ឋភាព​រហូត​ដល់​អ្នក​ផ្ដាច់។ ប៉ះ ហើយ​សង្កត់​ថយក្រោយ និង​ទិដ្ឋភាព​នៅ​ពេល​តែ​មួយ​ដើម្បី​ផ្ដាច់។"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"វា​រក្សាទុក​ក្នុង​ទិដ្ឋភាព​រហូត​ដល់​អ្នក​ផ្ដាច់។ ប៉ះ និង​សង្កត់​ទិដ្ឋភាព​ដើម្បី​ផ្ដាច់។"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"យល់​ហើយ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"ទេ អរគុណ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"លាក់ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 2cf1697..484551b 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"ವಿಸ್ತರಿಸು"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ಸಂಕುಚಿಸು"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"ಪರದೆಯನ್ನು ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"ನೀವು ಅನ್‌ಪಿನ್ ಮಾಡುವವರೆಗೆ ಅದನ್ನು ವೀಕ್ಷಣೆಯಲ್ಲಿಡುತ್ತದೆ. ಅನ್‌ಪಿನ್ ಮಾಡಲು ಒಂದೇ ಸಮಯದಲ್ಲಿ ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಒತ್ತಿ ಹಿಡಿದುಕೊಳ್ಳಿ ಹಾಗೂ ಅವಲೋಕಿಸಿ."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ನೀವು ಅನ್‌ಪಿನ್ ಮಾಡುವವರೆಗೆ ಅದನ್ನು ವೀಕ್ಷಣೆಯಲ್ಲಿಡುತ್ತದೆ. ಅನ್‌ಪಿನ್ ಮಾಡಲು ಅವಲೋಕನವನ್ನು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"ತಿಳಿಯಿತು"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"ಧನ್ಯವಾದಗಳು"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ಮರೆಮಾಡುವುದೇ?"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 4e8be28..c7f5489 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"펼치기"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"접기"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"화면 고정됨"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"고정 해제하기 전까지 계속 표시됩니다. 고정 해제하려면 뒤로와 최근 사용을 동시에 길게 터치합니다."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"고정 해제하기 전까지 계속 표시됩니다. 고정 해제하려면 최근 사용을 길게 터치합니다."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"확인"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"거부"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>을(를) 숨기시겠습니까?"</string>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index fc6897f..f57c9fc 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Жайып көрсөтүү"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жыйнап коюу"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Экран кадалган"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Бул бошотулмайынча көрүнө берет. Бошотуу үчүн, бир убакта Артка жана Карап чыгууну коё бербей басып туруңуз."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Бул бошотулмайынча көрүнө берет. Бошотуу үчүн, Карап чыгууну коё бербей басып туруңуз."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Түшүндүм"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Жок, рахмат"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> жашырылсынбы?"</string>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 6bb2043..18f3111 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"ຂະຫຍາຍ"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ຫຍໍ້ລົງ"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"ປັກ​ໝຸດໜ້າ​ຈໍ​ແລ້ວ"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"ມັນ​ຈະ​ຮັກ​ສາ​ໜ້າ​ຈໍ​ໄວ້​ໃນ​ມຸມມອງ​ຂອງ​ທ່ານ​ຈົນ​ກວ່າ​ທ່ານ​ຈະ​ຖອດ​ໝຸດ. ​ແຕະ​ປຸ່ມ ກັບ​ຄືນ ແລະ ພາບ​ຮວມ​ ຄ້າງ​ໄວ້​ພ້ອມ​ກັນ​ເພື່ອ​ຖອດ​ໝຸດ."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ມັນ​ຈະ​ຮັກ​ສາ​ໜ້າ​ຈໍ​ໄວ້​ໃນ​ມຸມມອງ​ຂອງ​ທ່ານ​ຈົນ​ກວ່າ​ທ່ານ​ຈະ​ຖອດ​ໝຸດ. ​ແຕະ​ປຸ່ມ ພາບ​ຮວມ​ ຄ້າງ​ໄວ້​ເພື່ອ​ຖອດ​ໝຸດ."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"ເຂົ້າໃຈແລ້ວ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"ບໍ່, ຂອບໃຈ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ເຊື່ອງ <xliff:g id="TILE_LABEL">%1$s</xliff:g> ຫຼື​ບໍ່?"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 8883cb6..307b7d9 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Išskleisti"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sutraukti"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekranas prisegtas"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Tai bus rodoma, kol atsegsite. Kad atsegtumėte, tuo pačiu metu palieskite ir laikykite „Atgal“ ir „Apžvalga“."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Tai bus rodoma, kol atsegsite. Kad atsegtumėte, palieskite ir laikykite „Apžvalga“."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Supratau"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, ačiū"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Slėpti „<xliff:g id="TILE_LABEL">%1$s</xliff:g>“?"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 47ca06a..3660cda 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -394,8 +394,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Izvērst"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sakļaut"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekrāns ir piesprausts"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Šādi tas būs redzams līdz brīdim, kad to atspraudīsiet. Lai atspraustu, vienlaikus pieskarieties vienumiem “Atpakaļ” un “Pārskats” un turiet tos nospiestus."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Šādi tas būs redzams līdz brīdim, kad to atspraudīsiet. Lai atspraustu, pieskarieties vienumam “Pārskats” un turiet to nospiestu."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Sapratu!"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nē, paldies"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vai paslēpt vienumu <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index 891ffd8..f8d0417 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Собери"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Екранот е прикачен"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ќе се гледа сè додека не го откачите. Допрете и држете Назад и Краток преглед истовремено за откачување."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ќе се гледа сè додека не го откачите. Допрете и држете Краток преглед за откачување."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Сфатив"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Не, фала"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Сокриј <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index 64d4184..516e846 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"വികസിപ്പിക്കുക"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ചുരുക്കുക"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"സ്‌ക്രീൻ പിൻ ചെയ്‌തു"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"നിങ്ങൾ അൺപിൻ ചെയ്യുന്നതുവരെ ഇത് കാണുന്ന വിധത്തിൽ നിലനിർത്തുന്നു. അൺപിൻ ചെയ്യാൻ \'മടങ്ങുക\', \'ചുരുക്കവിവരണം\' എന്നിവ ഒരേ സമയം സ്‌പർശിച്ച് പിടിക്കുക."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"നിങ്ങൾ അൺപിൻ ചെയ്യുന്നതുവരെ ഇത് കാണുന്ന വിധത്തിൽ നിലനിർത്തുന്നു. അൺപിൻ ചെയ്യുന്നതിന് \'ചുരുക്കവിവരണം\' സ്‌പർശിച്ചുപിടിക്കുക."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"മനസ്സിലായി"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"വേണ്ട, നന്ദി"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> എന്നത് മറയ്‌ക്കണോ?"</string>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 740ee0d..8cb8df1 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -391,8 +391,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Дэлгэх"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Хураах"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Дэлгэц эхэнд байрлуулагдсан"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Таныг эхэнд нээхийг болиулах хүртэл харагдана. Хүрээд, Back дээр удаан дараад хаахдаа Overview-ийг дар"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Таныг эхэнд нээхийг болиулах хүртэл харагдана. Хаахын тулд хүрээдOverview-ийг дар"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Ойлголоо"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Үгүй"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>-ийг нуух уу?"</string>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index 00d7bc8..cfa6b5f 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करा"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संकुचित करा"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"स्क्रीन पिन केलेली आहे"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"हे आपण अनपिन करेपर्यंत दृश्यामध्ये ते ठेवते. अनपिन करण्यासाठी एकाच वेळी परत आणि अलीकडील ला स्पर्श करा आणि धरून ठेवा आणि विहंगावलोकन करा."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"हे आपण अनपिन करेपर्यंत दृश्यामध्ये ते ठेवते. अनपिन करण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"समजले"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"नाही धन्यवाद"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> लपवायचे?"</string>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index 10865dd..6ff9a61 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Kembangkan"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Runtuhkan"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skrin telah disemat"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ini akan memastikan skrin kelihatan sehingga anda menyahsemat. Sentuh dan tahan Kembali dan Gambaran Keseluruhan pada masa yang sama untuk menyahsemat."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ini akan memastikan skrin kelihatan sehingga anda menyahsemat. Sentuh dan tahan Gambaran Keseluruhan untuk menyahsemat."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Faham"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Tidak"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Sembunyikan <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 5cb6087..e4b3e54 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"တိုးချဲ့ရန်"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ခေါက်သိမ်းရန်..."</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"မျက်နှာပြင် ပင်ထိုးပြီးပါပြီ"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"သင်ပင်ဖြုတ်သည့် တိုင်အောင် ၎င်းအား မြင်ကွင်းတွင် ထားရှိပါမည်။ ပင်ဖြုတ်ရန် အနောက်နှင့် ခြုံငုံကြည့်ခြင်းကို ဖိ၍ နှိပ်ထားနိုင်သည်။"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"သင်ပင်ဖြုတ်သည့် တိုင်အောင် ၎င်းအား မြင်ကွင်းတွင် ထားရှိပါမည်။ ပင်ဖြုတ်ရန် ခြုံငုံကြည့်ခြင်းကို ဖိ၍ နှိပ်ထားနိုင်သည်။"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"အဲဒါ ရပြီ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"မလို ကျေးဇူးပဲ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ဝှက်မည်လား?"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 39b404a..2882d7a 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Utvid"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skjermen er låst"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"På denne måten blir skjermen synlig frem til du låser den opp. Trykk på og hold inne Tilbake og Oversikt samtidig for å låse opp."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"På denne måten blir skjermen synlig frem til du låser den opp. Trykk på og hold inne Tilbake og Oversikt for å låse opp."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Skjønner"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nei takk"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vil du skjule <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 2a312f3..19d84ce 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तार गर्नुहोस्"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त पार्नुहोस्"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"पर्दा राखेका छ"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन फिर्ता र सिंहावलोकन नै समय मा पकड।"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन गर्न सिंहावलोकन पकड।"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"बुझेँ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"धन्यवाद पर्दैन"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"लुकाउनुहुन्छ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
@@ -440,7 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string>
     <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string>
     <string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
-    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्नुहुन्छ?"</string>
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्ने हो?"</string>
     <string name="enable_bluetooth_message" msgid="9106595990708985385">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"सक्रिय पार्नुहोस्"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 6bbb340..95ddeab 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Uitvouwen"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Samenvouwen"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Scherm is vastgezet"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Het scherm blijft zichtbaar totdat u het u losmaakt. Houd \'Terug\' en \'Overzicht\' tegelijkertijd aangeraakt om het los te maken."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Het scherm blijft zichtbaar totdat u het u losmaakt. Houd \'Overzicht\' aangeraakt om het los te maken."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Ik snap het"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nee, bedankt"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> verbergen?"</string>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index 61b68c1..6cb50b3 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ਨਸ਼ਟ ਕਰੋ"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"ਸਕ੍ਰੀਨ ਪਿੰਨ ਕੀਤੀ"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"ਇਹ ਇਸਨੂੰ ਦ੍ਰਿਸ਼ ਵਿੱਚ ਰੱਖਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਅਨਪਿਨ ਨਹੀਂ ਕਰਦੇ। ਅਨਪਿਨ ਕਰਨ ਲਈ ਪਿੱਛੇ ਅਤੇ ਰੂਪ-ਰੇਖਾ ਨੂੰ ਇੱਕੋ ਸਮੇਂ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ।"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ਇਹ ਇਸਨੂੰ ਦ੍ਰਿਸ਼ ਵਿੱਚ ਰੱਖਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਅਨਪਿਨ ਨਹੀਂ ਕਰਦੇ। ਅਨਪਿਨ ਕਰਨ ਲਈ ਰੂਪ-ਰੇਖਾ ਨੂੰ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ।"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"ਸਮਝ ਗਿਆ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ਕੀ <xliff:g id="TILE_LABEL">%1$s</xliff:g> ਨੂੰ ਲੁਕਾਉਣਾ ਹੈ?"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index caa3081..1821cdd 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozwiń"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zwiń"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekran jest przypięty"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, kliknij i przytrzymaj jednocześnie Wstecz i Przegląd."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, kliknij i przytrzymaj Przegląd."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nie, dziękuję"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ukryć <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index b82eb32..68ecfc8 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ela é mantida à vista até que seja liberada. Toque em \"Voltar\" e \"Visão Geral\" e mantenha essas opções pressionadas ao mesmo tempo para liberar."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ela é mantida à vista até que seja liberada. Toque em \"Visão geral\" e mantenha essa opção pressionada para liberar."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Esconder <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index c137908..61a76f9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Reduzir"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"O ecrã está fixado"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Será mantido na vista até soltar. Toque sem soltar em Anterior e Vista geral em simultâneo para soltar."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Será mantido na vista até soltar. Toque sem soltar em Vista geral para soltar."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Compreendi"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Pretende ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index b82eb32..68ecfc8 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ela é mantida à vista até que seja liberada. Toque em \"Voltar\" e \"Visão Geral\" e mantenha essas opções pressionadas ao mesmo tempo para liberar."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ela é mantida à vista até que seja liberada. Toque em \"Visão geral\" e mantenha essa opção pressionada para liberar."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Esconder <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index b5819ea..3af273d 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -255,7 +255,7 @@
     <string name="quick_settings_location_off_label" msgid="7464544086507331459">"Localizarea este dezactivată"</string>
     <string name="quick_settings_media_device_label" msgid="1302906836372603762">"Dispozitiv media"</string>
     <string name="quick_settings_rssi_label" msgid="7725671335550695589">"RSSI"</string>
-    <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Numai apeluri de urgenţă"</string>
+    <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Numai apeluri de urgență"</string>
     <string name="quick_settings_settings_label" msgid="5326556592578065401">"Setări"</string>
     <string name="quick_settings_time_label" msgid="4635969182239736408">"Ora"</string>
     <string name="quick_settings_user_label" msgid="5238995632130897840">"Eu"</string>
@@ -394,8 +394,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Extindeți"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Restrângeți"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ecranul este fixat"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ecranul este afișat până anulați fixarea. Apăsați lung pe Înapoi și pe Vizualizare generală simultan pentru a anula fixarea."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ecranul este afișat până anulați fixarea. Apăsați lung pe Vizualizare generală pentru a anula fixarea."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Am înțeles"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nu, mulțumesc"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ascundeți <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 0806684..296876c 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -397,8 +397,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Развернуть"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свернуть"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Блокировка в приложении включена"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Это приложение останется активным, пока вы не отмените блокировку, одновременно нажав кнопки \"Назад\" и \"Обзор\"."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Приложение останется активным, пока вы не отмените блокировку, одновременно нажав кнопки Назад и Обзор."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"ОК"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Нет, спасибо"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Скрыть параметр \"<xliff:g id="TILE_LABEL">%1$s</xliff:g>\"?"</string>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index 39147ff..185fc05 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -393,8 +393,7 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"දිග හරින්න"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"හකුළන්න"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"තීරය අමුණන ලදි"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"ඔබ ගලවන තෙක් එය දර්ශනය තුළ මෙය තබයි. ගැලවීමට ආපසු සහ දළ විශ්ලේෂණය එකම වේලාවක ස්පර්ෂ කර අල්ලා සිටින්න."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ඔබ ගලවන තෙක් එය දර්ශනය තුළ මෙය තබයි. ගැලවීමට දළ විශ්ලේෂණය ස්පර්ෂ කර අල්ලා සිටින්න."</string>
+    <string name="screen_pinning_description" msgid="3577937698406151604">"මෙය ඔබ ගලවන තෙක් එය දසුන තුළ තබයි. ගැලවීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න."</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"හරි, තේරුණා"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"එපා ස්තූතියි"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> සඟවන්නද?"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 86c8fe2..ebfd035 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -397,8 +397,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbaliť"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zbaliť"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je pripnutá"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Ak ho chcete uvoľniť, stlačte a podržte súčasne tlačidlá Späť a Prehľad."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Uvoľníte ho stlačením a podržaním tlačidla Prehľad."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Dobre"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nie, vďaka"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Skryť <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index c5dd188..d87b9a4 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Razširi"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Strni"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je pripet"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"S tem ostane zaslon v pogledu, dokler ga ne odpnete. Hkrati pridržite tipko za nazaj in tipko za pregled, če ga želite odpeti."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"S tem ostane zaslon v pogledu, dokler ga ne odpnete. Pridržite tipko za pregled, če ga želite odpeti."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Razumem"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, hvala"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Želite skriti <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index fcd65f7..5519e6b 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Zgjeroje"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Mbylle"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekrani u gozhdua"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Kjo e ruan në pamje derisa e heq nga gozhdimi. Prek dhe mbaj shtypur njëkohësisht \"Prapa\" dhe \"Përmbledhje\" për ta hequr nga gozhdimi."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Kjo e ruan në pamje derisa e heq nga gozhdimi. Prek dhe mbaj shtypur njëkohësisht \"Përmbledhje\" për ta hequr nga gozhdimi."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"E kuptova!"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Jo, faleminderit!"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Të fshihet <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 2c55a5c..67959f5 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -394,8 +394,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Скупи"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Екран је закачен"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"На овај начин ово остаје приказано док га не откачите. Истовремено додирните и задржите Назад и Преглед да бисте га откачили."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"На овај начин ово остаје приказано док га не откачите. Додирните и задржите Преглед да бисте га откачили."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Важи"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Не, хвала"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Желите ли да сакријете <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index e8b1765..48fe512 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Utöka"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Komprimera"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skärmen har fästs"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Med den här funktionen är skärmen synlig tills du lossar den. Tryck länge på Tillbaka och Översikt samtidigt om du vill lossa skärmen."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Detta visar skärmen tills du lossar den. Tryck länge på Översikt om du vill lossa skärmen."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tack"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vill du dölja <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index bbe271d..2b65540 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Panua"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Kunja"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skrini imebandikwa"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Hii itaendelea kuonyesha hadi ubandue. Gusa na ushikilie Nyuma na Muhtasari kwa wakati mmoja ili ubandue."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Hii itaendelea kuonyesha hadi uibandue. Gusa na ushikilie Muhtasari ili ubandue."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Nimeelewa"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Hapana, asante"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ungependa kuficha <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index 08bef13..1f4a4a2 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"விரிவாக்கு"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"சுருக்கு"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"திரை பொருத்தப்பட்டது"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"பொருத்தியதை விலக்கும்வரை இதைக் காட்சியில் வைக்கும். விலக்க, பின் மற்றும் மேலோட்டப் பார்வையை ஒரே நேரத்தில் தொட்டுப் பிடிக்கவும்."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"பொருத்தியதை விலக்கும்வரை இதைக் காட்சியில் வைக்கும். விலக்க, மேலோட்டப் பார்வையைத் தொட்டுப் பிடிக்கவும்."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"புரிந்தது"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"வேண்டாம்"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>ஐ மறைக்கவா?"</string>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index 46bc704..d0b3439 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"విస్తరింపజేయండి"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"కుదించండి"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"స్క్రీన్ పిన్ చేయబడింది"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"ఇది మీరు అన్‌పిన్ చేసే వరకు దీన్ని వీక్షణలో ఉంచుతుంది. అన్‌పిన్ చేయడానికి వెనుకకు మరియు స్థూలదృష్టిని ఒకేసారి తాకి, ఉంచండి."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ఇది మీరు అన్‌పిన్ చేసే వరకు దీన్ని వీక్షణలో ఉంచుతుంది. అన్‌పిన్ చేయడానికి స్థూలదృష్టిని తాకి, ఉంచండి."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"అర్థమైంది"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"వద్దు, ధన్యవాదాలు"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>ని దాచాలా?"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f7e7be0..cb2921d 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"ขยาย"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ยุบ"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"ตรึงหน้าจอแล้ว"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"การดำเนินการนี้จะเปิดหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"กลับ\" และ \"ภาพรวม\" พร้อมกันค้างไว้เพื่อเลิกตรึง"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"การดำเนินการนี้จะเปิดหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"ภาพรวม\" ค้างไว้เพื่อเลิกตรึง"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"รับทราบ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"ไม่เป็นไร ขอบคุณ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ซ่อน <xliff:g id="TILE_LABEL">%1$s</xliff:g> ไหม"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 92a6cdc..43969e9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Palawakin"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"I-collapse"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Naka-pin ang screen"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Pinapanatili nitong nasa view ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Bumalik at Pangkalahatang-ideya nang sabay upang mag-unpin."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Pinapanatili nitong nasa view ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Pangkalahatang-ideya upang mag-unpin."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Nakuha ko"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Hindi, salamat na lang"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Itago ang <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 2b9d6da..7a044ef 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişlet"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Daralt"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekran sabitlendi"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Böylece siz sabitlemesini kaldırana kadar görüntülenmeye devam eder. Sabitlemeyi kaldırmak için Geri ve Genel Bakış öğesine aynı anda dokunun ve basılı tutun."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Böylece siz sabitlemesini kaldırana kadar görüntülenmeye devam eder. Sabitlemeyi kaldırmak için Genel Bakış\'a dokunun ve basılı tutun."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Anladım"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Hayır, teşekkürler"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> gizlensin mi?"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index c040439..37030ac 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Розгорнути"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Згорнути"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Екран закріплено"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Закріпить екран, щоб ви могли постійно його бачити, доки не відкріпите. Щоб відкріпити, одночасно натисніть і втримуйте кнопки \"Назад\" і \"Огляд\"."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Закріпить екран, щоб ви могли постійно його бачити, доки не відкріпите. Щоб відкріпити, натисніть і втримуйте кнопку \"Огляд\"."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Зрозуміло"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ні, дякую"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Сховати <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index b0a5336..0712f98 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"پھیلائیں"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"سکیڑیں"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"اسکرین پن کردہ ہے"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"یہ اس کو اس وقت تک منظر میں رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے واپس اور عمومی جائزہ کو ایک ساتھ ٹچ کریں اور پکڑ کر رکھیں۔"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"یہ اس کو اس وقت تک منظر میں رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے عمومی جائزہ کو ٹچ کریں اور پکڑ کر رکھیں۔"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"سمجھ آ گئی"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"نہیں شکریہ"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> کو چھپائیں؟"</string>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index 9b9e2cd..ad3dcc7 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Yoyish"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yig‘ish"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Ekran qadaldi"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Ekran yechilmaguncha u qadalgan holatda qoladi. Uni yechish uchun “Orqaga” va “Umumiy nazar” tugmalarini bir vaqtda bosing va ushlab turing."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ekran yechilmaguncha u qadalgan holatda qoladi. Uni yechish uchun “Umumiy nazar” tugmasini bosing va ushlab turing."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Yo‘q, kerakmas"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> berkitilsinmi?"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index d4ed7a5..f13033b 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Mở rộng"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Thu gọn"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Màn hình được ghim"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Chạm và giữ nút Quay lại và Tổng quan cùng một lúc để bỏ ghim."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Chạm và giữ nút Quay lại để bỏ ghim."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Ok"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Không, cảm ơn"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ẩn <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 05416ca..ade7636 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"展开"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"收起"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"已固定屏幕"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"这将会固定显示此屏幕,直到您取消固定为止。触摸并同时按住“返回”和“概览”即可取消固定屏幕。"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"这将会固定显示此屏幕,直到您取消固定为止。触摸并按住“概览”即可取消固定屏幕。"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"不用了"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"要隐藏“<xliff:g id="TILE_LABEL">%1$s</xliff:g>”吗?"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index ba4d3cb..aa6e065c 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"在您取消固定前,它會保持在檢視狀態。輕觸並同時按住 [返回] 和 [概覽],即可取消固定。"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"在您取消固定前,它會保持在檢視狀態。輕觸並按住 [概覽] 即可取消固定。"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"不用了,謝謝"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"隱藏 <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6e0c852..ffaaefe 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -395,8 +395,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。同時按住返回按鈕和總覽按鈕即可取消固定。"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。按住總覽按鈕即可取消固定。"</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"不用了,謝謝"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"隱藏<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 147f6b6..f1df714 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -393,8 +393,8 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Nweba"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Goqa"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Isikrini siphiniwe"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Lokhu kukugcina kubukeka uze ususe ukuphina. Thinta futhi ubambe u-Emuva no-Ukubuka konke ngesikhathi esisodwa ukuze ususe ukuphina."</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Lokhu kukugcina kubukeka uze ususe ukuphina. Thinta futhi ubambe u-Ukubuka konke ukuze ususe ukuphina."</string>
+    <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+    <skip />
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Ngiyitholile"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Cha ngiyabonga"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Fihla i-<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bae8017..0c638a2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -136,7 +136,7 @@
     <integer name="touch_acceptance_delay">700</integer>
 
     <!-- The duration in seconds to wait before the dismiss buttons are shown. -->
-    <integer name="recents_task_bar_dismiss_delay_seconds">1</integer>
+    <integer name="recents_task_bar_dismiss_delay_seconds">1000</integer>
 
     <!-- The min animation duration for animating views that are currently visible. -->
     <integer name="recents_filter_animate_current_views_duration">250</integer>
@@ -269,9 +269,6 @@
     <!-- Duration of the expansion animation in the volume dialog -->
     <item name="volume_expand_animation_duration" type="integer">300</item>
 
-    <!-- Whether to show a "shelf" of apps at the bottom of the screen. -->
-    <bool name="config_enableAppShelf">false</bool>
-
     <!-- Whether to show the full screen user switcher. -->
     <bool name="config_enableFullscreenUserSwitcher">false</bool>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index cbc92f2..a4137b9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -128,6 +128,8 @@
     <dimen name="qs_new_tile_height">100dp</dimen>
     <dimen name="qs_quick_actions_height">88dp</dimen>
     <dimen name="qs_quick_actions_padding">25dp</dimen>
+    <dimen name="qs_quick_tile_size">48dp</dimen>
+    <dimen name="qs_quick_tile_padding">12dp</dimen>
     <dimen name="qs_page_indicator_size">12dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
@@ -230,6 +232,9 @@
     <!-- The size of the lock-to-app button icon. -->
     <dimen name="recents_lock_to_app_icon_size">28dp</dimen>
 
+    <!-- The amount to allow the stack to overscroll. -->
+    <dimen name="recents_stack_overscroll">24dp</dimen>
+
     <!-- Space reserved for the cards behind the top card in the top stack -->
     <dimen name="top_stack_peek_amount">12dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3a5680d..7c86f96 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -999,9 +999,7 @@
     <!-- Screen pinning dialog title. -->
     <string name="screen_pinning_title">Screen is pinned</string>
     <!-- Screen pinning dialog description. -->
-    <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin.</string>
-    <!-- Screen pinning dialog description when in accessibility mode. -->
-    <string name="screen_pinning_description_accessible">This keeps it in view until you unpin. Touch and hold Overview to unpin.</string>
+    <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back to unpin.</string>
     <!-- Screen pinning positive response. -->
     <string name="screen_pinning_positive">Got it</string>
     <!-- Screen pinning negative response. -->
diff --git a/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java b/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java
new file mode 100644
index 0000000..3362650
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageView;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileBaseView;
+
+public class QSQuickTileView extends QSTileBaseView {
+
+    private final int mPadding;
+    private final ImageView mIcon;
+
+    public QSQuickTileView(Context context) {
+        super(context);
+        mPadding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
+        mIcon = createIcon();
+        addView(mIcon);
+    }
+
+    protected ImageView createIcon() {
+        final ImageView icon = new ImageView(mContext);
+        icon.setId(android.R.id.icon);
+        icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+        return icon;
+    }
+
+    @Override
+    public void init(OnClickListener click, OnClickListener clickSecondary,
+                     OnLongClickListener longClick) {
+        setClickable(true);
+        setOnClickListener(click);
+    }
+
+    @Override
+    protected void handleStateChanged(QSTile.State state) {
+        mIcon.setImageDrawable(state.icon.getDrawable(getContext()));
+        setContentDescription(state.contentDescription);
+    }
+
+    @Override
+    public boolean setType(int type) {
+        return false;
+    }
+
+    @Override
+    public View updateAccessibilityOrder(View previousView) {
+        return this;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mIcon.measure(exactly(getMeasuredWidth() - 2 * mPadding),
+                exactly(getMeasuredHeight() - 2 * mPadding));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        layout(mIcon, mPadding, mPadding);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 1d0bfe7..9a4cd93 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -27,4 +27,9 @@
     void cancelPreloadingRecents();
     void showNextAffiliatedTask();
     void showPrevAffiliatedTask();
+
+    /**
+     * Docks the top-most task and opens recents.
+     */
+    void dockTopTask();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index c6bcfc4..794e900 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -566,7 +566,7 @@
 
     public static final class TileRecord extends Record {
         public QSTile<?> tile;
-        public QSTileView tileView;
+        public QSTileBaseView tileView;
         public int row;
         public int col;
         public boolean scanState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 61cb224..5d928d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -98,7 +98,7 @@
         return mHost;
     }
 
-    public QSTileView createTileView(Context context) {
+    public QSTileBaseView createTileView(Context context) {
         return new QSTileView(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
new file mode 100644
index 0000000..72fc88d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+public abstract class QSTileBaseView extends ViewGroup {
+
+    public static final int QS_TYPE_NORMAL = 0;
+    public static final int QS_TYPE_DUAL   = 1;
+    public static final int QS_TYPE_QUICK  = 2;
+
+    private final H mHandler = new H();
+
+    public QSTileBaseView(Context context) {
+        super(context);
+    }
+
+    public QSTileBaseView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+
+    public void onStateChanged(QSTile.State state) {
+        mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
+    }
+
+    public abstract void init(OnClickListener click, OnClickListener clickSecondary,
+                              OnLongClickListener longClick);
+    public abstract View updateAccessibilityOrder(View previousView);
+    public abstract boolean setType(int type);
+
+    protected abstract void handleStateChanged(QSTile.State state);
+
+    protected static int exactly(int size) {
+        return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+    }
+
+    protected static void layout(View child, int left, int top) {
+        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
+    }
+
+    private class H extends Handler {
+        private static final int STATE_CHANGED = 1;
+        public H() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == STATE_CHANGED) {
+                handleStateChanged((QSTile.State) msg.obj);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index d914beb..cc264a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -44,18 +44,13 @@
 import java.util.Objects;
 
 /** View that represents a standard quick settings tile. **/
-public class QSTileView extends ViewGroup {
+public class QSTileView extends QSTileBaseView {
     private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
             Typeface.NORMAL);
 
-    public static final int QS_TYPE_NORMAL = 0;
-    public static final int QS_TYPE_DUAL   = 1;
-    public static final int QS_TYPE_QUICK  = 2;
-
     protected final Context mContext;
     private final View mIcon;
     private final View mDivider;
-    private final H mHandler = new H();
     private final int mIconSizePx;
     private final int mTileSpacingPx;
     private int mTilePaddingTopPx;
@@ -291,10 +286,6 @@
         return MeasureSpec.EXACTLY;
     }
 
-    private static int exactly(int size) {
-        return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
-    }
-
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         final int w = getMeasuredWidth();
@@ -334,10 +325,6 @@
         mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
     }
 
-    private static void layout(View child, int left, int top) {
-        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
-    }
-
     protected void handleStateChanged(QSTile.State state) {
         if (mIcon instanceof ImageView) {
             setIcon((ImageView) mIcon, state);
@@ -369,10 +356,6 @@
         }
     }
 
-    public void onStateChanged(QSTile.State state) {
-        mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
-    }
-
     /**
      * Update the accessibility order for this view.
      *
@@ -392,17 +375,4 @@
         firstView.setAccessibilityTraversalAfter(previousView.getId());
         return lastView;
     }
-
-    private class H extends Handler {
-        private static final int STATE_CHANGED = 1;
-        public H() {
-            super(Looper.getMainLooper());
-        }
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == STATE_CHANGED) {
-                handleStateChanged((State) msg.obj);
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index a2d9ef0..6a053be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -58,6 +60,7 @@
         ArrayList<QSTile<?>> quickTiles = new ArrayList<>();
         for (QSTile<?> tile : tiles) {
             if (tile.getTileType() == QSTileView.QS_TYPE_QUICK) {
+                Log.d("QSPanel", "Adding " + tile.getTileSpec());
                 quickTiles.add(tile);
             }
             if (quickTiles.size() == 2) {
@@ -71,16 +74,17 @@
 
         public HeaderTileLayout(Context context) {
             super(context);
+            setGravity(Gravity.CENTER_VERTICAL);
             setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-            int qsCompensation = (int)
-                    context.getResources().getDimension(R.dimen.qs_header_neg_padding);
-            setPadding(0, qsCompensation, 0, 0);
+
+            int padding =
+                    mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
             ImageView downArrow = new ImageView(context);
             downArrow.setImageResource(R.drawable.ic_expand_more);
             downArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
                     android.R.color.white, null)));
             downArrow.setLayoutParams(generateLayoutParams());
-            downArrow.setPadding(0, -qsCompensation, 0, 0);
+            downArrow.setPadding(padding, padding, padding, padding);
             addView(downArrow);
             setOrientation(LinearLayout.HORIZONTAL);
         }
@@ -95,7 +99,9 @@
         }
 
         private LayoutParams generateLayoutParams() {
-            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT);
+            int size =
+                    mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
+            LayoutParams lp = new LayoutParams(0, size);
             lp.weight = 1;
             return lp;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index f3ad9d8..e2d2ffb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -27,13 +27,12 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTileView;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataController;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
 
 /** Quick settings tile: Cellular **/
@@ -74,7 +73,7 @@
     }
 
     @Override
-    public QSTileView createTileView(Context context) {
+    public QSTileBaseView createTileView(Context context) {
         return new SignalTileView(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java
index 13ccaaa..b77191e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 
-/** Quick settings tile: Wifi **/
+/** Quick settings tile: Airplane mode **/
 public class QAirplaneTile extends AirplaneModeTile {
 
     public QAirplaneTile(Host host) {
@@ -26,6 +29,11 @@
     }
 
     @Override
+    public QSTileBaseView createTileView(Context context) {
+        return new QSQuickTileView(context);
+    }
+
+    @Override
     public int getTileType() {
         return QSTileView.QS_TYPE_QUICK;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java
index 02975cb..4fe7e45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 
 /** Quick settings tile: Bluetooth **/
@@ -26,6 +29,11 @@
     }
 
     @Override
+    public QSTileBaseView createTileView(Context context) {
+        return new QSQuickTileView(context);
+    }
+
+    @Override
     public int getTileType() {
         return QSTileView.QS_TYPE_QUICK;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java
index 31035cd..e115755e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 
-/** Quick settings tile: Wifi **/
+/** Quick settings tile: Flashlight **/
 public class QFlashlightTile extends FlashlightTile {
 
     public QFlashlightTile(Host host) {
@@ -26,6 +29,11 @@
     }
 
     @Override
+    public QSTileBaseView createTileView(Context context) {
+        return new QSQuickTileView(context);
+    }
+
+    @Override
     public int getTileType() {
         return QSTileView.QS_TYPE_QUICK;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
index 3675f02..8b3013a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
@@ -15,11 +15,14 @@
  */
 package com.android.systemui.qs.tiles;
 
+import android.content.Context;
 import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.R;
 
 public class QLockTile extends QSTile<QSTile.State> implements KeyguardMonitor.Callback {
 
@@ -31,6 +34,11 @@
     }
 
     @Override
+    public QSTileBaseView createTileView(Context context) {
+        return new QSQuickTileView(context);
+    }
+
+    @Override
     public int getTileType() {
         return QSTileView.QS_TYPE_QUICK;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java
index e066bab..5f5cab5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 
 /** Quick settings tile: Rotation **/
@@ -26,6 +29,11 @@
     }
 
     @Override
+    public QSTileBaseView createTileView(Context context) {
+        return new QSQuickTileView(context);
+    }
+
+    @Override
     public int getTileType() {
         return QSTileView.QS_TYPE_QUICK;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java
index b157275..f0fe87d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.statusbar.policy.WifiIcons;
 
@@ -27,6 +30,11 @@
     }
 
     @Override
+    public QSTileBaseView createTileView(Context context) {
+        return new QSQuickTileView(context);
+    }
+
+    @Override
     public int getTileType() {
         return QSTileView.QS_TYPE_QUICK;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 91e0218..3763618 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -94,7 +95,7 @@
     }
 
     @Override
-    public QSTileView createTileView(Context context) {
+    public QSTileBaseView createTileView(Context context) {
         return new SignalTileView(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index cdb6b93..ebfacac 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -37,6 +37,8 @@
             public static final boolean EnableTaskFiltering = false;
             // Enables dismiss-all
             public static final boolean EnableDismissAll = false;
+            // Enables fast-toggling
+            public static final boolean EnableFastToggleRecents = false;
             // Enables the thumbnail alpha on the front-most task
             public static final boolean EnableThumbnailAlphaOnFrontmost = false;
             // This disables the search bar integration
@@ -62,8 +64,6 @@
         }
 
         public static class TaskStackView {
-            public static final int TaskStackMinOverscrollRange = 32;
-            public static final int TaskStackMaxOverscrollRange = 128;
             public static final int FilterStartDelay = 25;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index c216f97..4d40cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -54,6 +54,7 @@
 
     private static SystemServicesProxy sSystemServicesProxy;
     private static RecentsTaskLoader sTaskLoader;
+    private static RecentsConfiguration sConfiguration;
 
     private Handler mHandler;
     private RecentsImpl mImpl;
@@ -129,10 +130,15 @@
         return sSystemServicesProxy;
     }
 
+    public static RecentsConfiguration getConfiguration() {
+        return sConfiguration;
+    }
+
     @Override
     public void start() {
         sSystemServicesProxy = new SystemServicesProxy(mContext);
         sTaskLoader = new RecentsTaskLoader(mContext);
+        sConfiguration = new RecentsConfiguration(mContext);
         mHandler = new Handler();
         mImpl = new RecentsImpl(mContext);
 
@@ -284,6 +290,11 @@
     }
 
     @Override
+    public void dockTopTask() {
+        mImpl.dockTopTask();
+    }
+
+    @Override
     public void showNextAffiliatedTask() {
         mImpl.showNextAffiliatedTask();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c416967..6c8bf0f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -32,6 +32,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewStub;
@@ -41,16 +42,21 @@
 import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.ResizeTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
 import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsPackageMonitor;
@@ -69,9 +75,11 @@
  */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks {
 
+    private final static String TAG = "RecentsActivity";
+    private final static boolean DEBUG = false;
+
     public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
 
-    RecentsConfiguration mConfig;
     RecentsPackageMonitor mPackageMonitor;
     long mLastTabKeyEventTime;
     boolean mFinishedOnStartup;
@@ -96,6 +104,14 @@
     // Runnable to be executed after we paused ourselves
     Runnable mAfterPauseRunnable;
 
+    // The trigger to automatically launch the current task
+    DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
+        @Override
+        public void run() {
+            boolean dismissed = dismissRecentsToFocusedTask(false);
+        }
+    });
+
     /**
      * A common Runnable to finish Recents either by calling finish() (with a custom animation) or
      * launching Home with some ActivityOptions.  Generally we always launch home when we exit
@@ -157,7 +173,8 @@
         }
 
         // Start loading tasks according to the load plan
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         if (!plan.hasTasks()) {
             loader.preloadTasks(plan, launchState.launchedFromHome);
         }
@@ -244,9 +261,27 @@
         MetricsLogger.histogram(this, "overview_task_count", taskCount);
     }
 
-    /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
+    /**
+     * Dismisses recents if we are already visible and the intent is to toggle the recents view.
+     */
+    boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
+            // If we currently have filtered stacks, then unfilter those first
+            if (checkFilteredStackState &&
+                    mRecentsView.unfilterFilteredStacks()) return true;
+            // If we have a focused Task, launch that Task now
+            if (mRecentsView.launchFocusedTask()) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Dismisses recents if we are already visible and the intent is to toggle the recents view.
+     */
     boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // If we currently have filtered stacks, then unfilter those first
@@ -318,7 +353,6 @@
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
         // Initialize the widget host (the host id is static and does not change)
-        mConfig = RecentsConfiguration.getInstance();
         if (!Constants.DebugFlags.App.DisableSearchBar) {
             mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
         }
@@ -365,7 +399,8 @@
         // If this is a new instance from a configuration change, then we have to manually trigger
         // the enter animation state, or if recents was relaunched by AM, without going through
         // the normal mechanisms
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
                 !launchState.launchedFromAppWithThumbnail;
         if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
@@ -390,6 +425,11 @@
             mRecentsView.post(mAfterPauseRunnable);
             mAfterPauseRunnable = null;
         }
+
+        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+            // Stop the fast-toggle dozer
+            mIterateTrigger.stopDozing();
+        }
     }
 
     @Override
@@ -403,7 +443,8 @@
         // Workaround for b/22542869, if the RecentsActivity is started again, but without going
         // through SystemUI, we need to reset the config launch flags to ensure that we do not
         // wait on the system to send a signal that was never queued.
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         launchState.launchedFromHome = false;
         launchState.launchedFromSearchHome = false;
         launchState.launchedFromAppWithThumbnail = false;
@@ -467,22 +508,27 @@
                 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
                     // Focus the next task in the stack
                     final boolean backward = event.isShiftPressed();
-                    mRecentsView.focusNextTask(!backward);
+                    if (backward) {
+                        EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
+                    } else {
+                        EventBus.getDefault().send(new FocusNextTaskViewEvent());
+                    }
                     mLastTabKeyEventTime = SystemClock.elapsedRealtime();
                 }
                 return true;
             }
             case KeyEvent.KEYCODE_DPAD_UP: {
-                mRecentsView.focusNextTask(true);
+                EventBus.getDefault().send(new FocusNextTaskViewEvent());
                 return true;
             }
             case KeyEvent.KEYCODE_DPAD_DOWN: {
-                mRecentsView.focusNextTask(false);
+                EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
                 return true;
             }
             case KeyEvent.KEYCODE_DEL:
             case KeyEvent.KEYCODE_FORWARD_DEL: {
-                mRecentsView.dismissFocusedTask();
+                EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
+
                 // Keep track of deletions by keyboard
                 MetricsLogger.histogram(this, "overview_task_dismissed_source",
                         Constants.Metrics.DismissSourceKeyboard);
@@ -542,6 +588,16 @@
         dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
     }
 
+    public final void onBusEvent(IterateRecentsEvent event) {
+        // Focus the next task
+        EventBus.getDefault().send(new FocusNextTaskViewEvent());
+        mIterateTrigger.poke();
+    }
+
+    public final void onBusEvent(UserInteractionEvent event) {
+        mIterateTrigger.stopDozing();
+    }
+
     public final void onBusEvent(HideRecentsEvent event) {
         if (event.triggeredFromAltTab) {
             // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
@@ -558,7 +614,7 @@
         // Try and start the enter animation (or restart it on configuration changed)
         ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
         ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
-        mRecentsView.startEnterRecentsAnimation(ctx);
+        ctx.postAnimationTrigger.increment();
         if (mSearchWidgetInfo != null) {
             ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
@@ -570,6 +626,21 @@
                 }
             });
         }
+        ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+            @Override
+            public void run() {
+                // If we are not launching with alt-tab and fast-toggle is enabled, then start
+                // the dozer now
+                RecentsConfiguration config = Recents.getConfiguration();
+                RecentsActivityLaunchState launchState = config.getLaunchState();
+                if (Constants.DebugFlags.App.EnableFastToggleRecents &&
+                        !launchState.launchedWithAltTab) {
+                    mIterateTrigger.startDozing();
+                }
+            }
+        });
+        mRecentsView.startEnterRecentsAnimation(ctx);
+        ctx.postAnimationTrigger.decrement();
     }
 
     public final void onBusEvent(AppWidgetProviderChangedEvent event) {
@@ -589,7 +660,7 @@
         MetricsLogger.count(this, "overview_app_info", 1);
     }
 
-    public final void onBusEvent(DismissTaskEvent event) {
+    public final void onBusEvent(DismissTaskViewEvent event) {
         // Remove any stored data from the loader
         RecentsTaskLoader loader = Recents.getTaskLoader();
         loader.deleteTaskData(event.task, false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index e2e0e918..aca816e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -28,8 +28,6 @@
  */
 public class RecentsActivityLaunchState {
 
-    public RecentsConfiguration mConfig;
-
     public boolean launchedWithAltTab;
     public boolean launchedWithNoRecentTasks;
     public boolean launchedFromAppWithThumbnail;
@@ -41,10 +39,6 @@
     public int launchedNumVisibleTasks;
     public int launchedNumVisibleThumbnails;
 
-    RecentsActivityLaunchState(RecentsConfiguration config) {
-        mConfig = config;
-    }
-
     /** Called when the configuration has changed, and we want to reset any configuration specific
      * members. */
     public void updateOnConfigurationChange() {
@@ -56,7 +50,7 @@
 
     /** Returns whether the status bar scrim should be animated when shown for the first time. */
     public boolean shouldAnimateStatusBarScrim() {
-        return launchedFromHome;
+        return true;
     }
 
     /** Returns whether the status bar scrim should be visible. */
@@ -72,6 +66,7 @@
     /** Returns whether the nav bar scrim should be visible. */
     public boolean hasNavBarScrim() {
         // Only show the scrim if we have recent tasks, and if the nav bar is not transposed
-        return !launchedWithNoRecentTasks && mConfig.hasTransposedNavBar;
+        RecentsConfiguration config = Recents.getConfiguration();
+        return !launchedWithNoRecentTasks && !config.hasTransposedNavBar;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index d8f4023..e2f20fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -28,7 +28,6 @@
  * tied to the current activity.
  */
 public class RecentsConfiguration {
-    static RecentsConfiguration sInstance;
 
     private static final int LARGE_SCREEN_MIN_DP = 600;
     private static final int XLARGE_SCREEN_MIN_DP = 720;
@@ -53,7 +52,7 @@
     public static final int SVELTE_DISABLE_LOADING = 3;
 
     // Launch states
-    public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState(this);
+    public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState();
 
     // TODO: Values determined by the current context, needs to be refactored into something that is
     //       agnostic of the activity context, but still calculable from the Recents component for
@@ -79,10 +78,10 @@
     /** Dev options and global settings */
     public boolean lockToAppEnabled;
 
-    /** Private constructor */
-    private RecentsConfiguration(Context context, SystemServicesProxy ssp) {
+    public RecentsConfiguration(Context context) {
         // Load only resources that can not change after the first load either through developer
         // settings or via multi window
+        SystemServicesProxy ssp = Recents.getSystemServices();
         Context appContext = context.getApplicationContext();
         Resources res = appContext.getResources();
         useHardwareLayers = res.getBoolean(R.bool.config_recents_use_hardware_layers);
@@ -121,19 +120,6 @@
         hasTransposedSearchBar = isLandscape && isLargeScreen && !isXLargeScreen;
     }
 
-    /** Updates the configuration to the current context */
-    public static RecentsConfiguration initialize(Context context, SystemServicesProxy ssp) {
-        if (sInstance == null) {
-            sInstance = new RecentsConfiguration(context, ssp);
-        }
-        return sInstance;
-    }
-
-    /** Returns the current recents configuration */
-    public static RecentsConfiguration getInstance() {
-        return sInstance;
-    }
-
     /**
      * Returns the activity launch state.
      * TODO: This will be refactored out of RecentsConfiguration.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 07c7897..d02e2af 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.recents;
 
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ITaskStackListener;
@@ -31,8 +33,10 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.MutableBoolean;
+import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.View;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -40,6 +44,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
@@ -134,7 +139,6 @@
     boolean mCanReuseTaskStackViews = true;
 
     // Task launching
-    RecentsConfiguration mConfig;
     Rect mSearchBarBounds = new Rect();
     Rect mTaskStackBounds = new Rect();
     Rect mLastTaskViewBounds = new Rect();
@@ -173,7 +177,6 @@
         ssp.registerTaskStackListener(mTaskStackListener);
 
         // Initialize the static configuration resources
-        mConfig = RecentsConfiguration.initialize(mContext, ssp);
         mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
         mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
         mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
@@ -204,7 +207,7 @@
     public void onConfigurationChanged() {
         // Don't reuse task stack views if the configuration changes
         mCanReuseTaskStackViews = false;
-        mConfig.updateOnConfigurationChange();
+        Recents.getConfiguration().updateOnConfigurationChange();
     }
 
     /**
@@ -265,26 +268,38 @@
         mTriggeredFromAltTab = false;
 
         try {
-            // If the user has toggled it too quickly, then just eat up the event here (it's better
-            // than showing a janky screenshot).
-            // NOTE: Ideally, the screenshot mechanism would take the window transform into account
-            if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
-                return;
-            }
-
-            // If Recents is the front most activity, then we should just communicate with it
-            // directly to launch the first task or dismiss itself
             SystemServicesProxy ssp = Recents.getSystemServices();
             ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
             MutableBoolean isTopTaskHome = new MutableBoolean(true);
             if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
-                // Notify recents to toggle itself
-                EventBus.getDefault().post(new ToggleRecentsEvent());
-                mLastToggleTime = SystemClock.elapsedRealtime();
+                if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+                    // Notify recents to move onto the next task
+                    EventBus.getDefault().post(new IterateRecentsEvent());
+                } else {
+                    // If the user has toggled it too quickly, then just eat up the event here (it's
+                    // better than showing a janky screenshot).
+                    // NOTE: Ideally, the screenshot mechanism would take the window transform into
+                    // account
+                    if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+                        return;
+                    }
+
+                    EventBus.getDefault().post(new ToggleRecentsEvent());
+                    mLastToggleTime = SystemClock.elapsedRealtime();
+                }
                 return;
             } else {
+                // If the user has toggled it too quickly, then just eat up the event here (it's
+                // better than showing a janky screenshot).
+                // NOTE: Ideally, the screenshot mechanism would take the window transform into
+                // account
+                if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+                    return;
+                }
+
                 // Otherwise, start the recents activity
                 startRecentsActivity(topTask, isTopTaskHome.value);
+                mLastToggleTime = SystemClock.elapsedRealtime();
             }
         } catch (ActivityNotFoundException e) {
             Console.logRawError("Failed to launch RecentAppsIntent", e);
@@ -333,7 +348,7 @@
         // Return early if there is no running task (can't determine affiliated tasks in this case)
         if (runningTask == null) return;
         // Return early if the running task is in the home stack (optimization)
-        if (ssp.isInHomeStack(runningTask.id)) return;
+        if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
 
         // Find the task in the recents list
         ArrayList<Task> tasks = focusedStack.getTasks();
@@ -405,6 +420,16 @@
         showRelativeAffiliatedTask(false);
     }
 
+    public void dockTopTask() {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+        if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
+            ssp.startTaskInDockedMode(topTask.id,
+                    ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+            showRecents(false /* triggeredFromAltTab */);
+        }
+    }
+
     /**
      * Returns the preloaded load plan and invalidates it.
      */
@@ -422,31 +447,32 @@
      *                               is not already bound (can be expensive)
      */
     private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
+        RecentsConfiguration config = Recents.getConfiguration();
         SystemServicesProxy ssp = Recents.getSystemServices();
         Rect windowRect = ssp.getWindowRect();
 
         // Update the configuration for the current state
-        mConfig.update(mContext, ssp, ssp.getWindowRect());
+        config.update(mContext, ssp, ssp.getWindowRect());
 
         if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
             // Try and pre-emptively bind the search widget on startup to ensure that we
             // have the right thumbnail bounds to animate to.
             // Note: We have to reload the widget id before we get the task stack bounds below
             if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
-                mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
+                config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
             }
         }
         Rect systemInsets = new Rect(0, mStatusBarHeight,
-                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
-                (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
-        mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
+                (config.hasTransposedNavBar ? mNavBarWidth : 0),
+                (config.hasTransposedNavBar ? 0 : mNavBarHeight));
+        config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
                 mSearchBarBounds, mTaskStackBounds);
 
         // Rebind the header bar and draw it for the transition
         TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
         Rect taskStackBounds = new Rect(mTaskStackBounds);
         algo.setSystemInsets(systemInsets);
-        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
+        algo.computeRects(taskStackBounds);
         Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
         if (!taskViewBounds.equals(mLastTaskViewBounds)) {
             mLastTaskViewBounds.set(taskViewBounds);
@@ -535,12 +561,44 @@
      */
     private ActivityOptions getThumbnailTransitionActivityOptions(
             ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
+        if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
+            ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
+            stackView.getScroller().setStackScrollToInitialState();
+            ArrayList<Task> tasks = stack.getTasks();
+            for (int i = tasks.size() - 1; i >= 0; i--) {
+                Task task = tasks.get(i);
+                if (SystemServicesProxy.isFreeformStack(task.key.stackId)) {
+                    mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+                            stackView.getScroller().getStackScroll(), mTmpTransform, null);
+                    Rect toTaskRect = new Rect();
+                    mTmpTransform.rect.round(toTaskRect);
+                    Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
+                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
+                }
+            }
+            AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
+            specs.toArray(specsArray);
+            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+                    specsArray, mHandler, this);
+        } else {
+            // Update the destination rect
+            Task toTask = new Task();
+            TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+                    topTask.id, toTask);
+            RectF toTaskRect = toTransform.rect;
+            Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
+            if (thumbnail != null) {
+                return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+                        thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
+                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
+            }
+            // If both the screenshot and thumbnail fails, then just fall back to the default transition
+            return getUnknownTransitionActivityOptions();
+        }
+    }
 
-        // Update the destination rect
-        Task toTask = new Task();
-        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                topTask.id, toTask);
-        RectF toTaskRect = toTransform.rect;
+    private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
+            TaskViewTransform toTransform) {
         Bitmap thumbnail;
         if (mThumbnailTransitionBitmapCacheKey != null
                 && mThumbnailTransitionBitmapCacheKey.key != null
@@ -552,14 +610,7 @@
             preloadIcon(topTask);
             thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
         }
-        if (thumbnail != null) {
-            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
-                    (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
-        }
-
-        // If both the screenshot and thumbnail fails, then just fall back to the default transition
-        return getUnknownTransitionActivityOptions();
+        return thumbnail;
     }
 
     /**
@@ -695,7 +746,8 @@
         mStartAnimationTriggered = false;
 
         // Update the configuration based on the launch options
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         launchState.launchedFromHome = fromSearchHome || fromHome;
         launchState.launchedFromSearchHome = fromSearchHome;
         launchState.launchedFromAppWithThumbnail = fromThumbnail;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 31ee8ad..28299d3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -234,14 +234,6 @@
         dismissAllowingStateLoss();
         mRecentsActivity.dismissRecentsToHomeWithoutTransitionAnimation();
 
-        // In debug mode, we force all task to be resizeable regardless of the
-        // current app configuration.
-        for (int i = additionalTasks; i >= 0; --i) {
-            if (mTasks[i] != null) {
-                ssp.setTaskResizeable(mTasks[i].key.id);
-            }
-        }
-
         // Show tasks as they might not be currently visible - beginning with the oldest so that
         // the focus ends on the selected one.
         for (int i = additionalTasks; i >= 0; --i) {
@@ -277,8 +269,7 @@
         if (mTasks[0].key.stackId != DOCKED_STACK_ID) {
             int taskId = mTasks[0].key.id;
             SystemServicesProxy ssp = Recents.getSystemServices();
-            ssp.setTaskResizeable(taskId);
-            ssp.dockTask(taskId, createMode);
+            ssp.startTaskInDockedMode(taskId, createMode);
             mRecentsView.launchTask(mTasks[0], null, DOCKED_STACK_ID);
         } else {
             Toast.makeText(getContext(), "Already docked", Toast.LENGTH_SHORT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 231843e..10075bc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -214,11 +214,8 @@
                         .setVisibility(View.INVISIBLE);
             }
 
-            final int description = mAccessibilityService.isEnabled()
-                    ? R.string.screen_pinning_description_accessible
-                    : R.string.screen_pinning_description;
             ((TextView) mLayout.findViewById(R.id.screen_pinning_description))
-                    .setText(description);
+                    .setText(R.string.screen_pinning_description);
             final int backBgVisibility =
                     mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE;
             mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index fec0fc5..deae4c8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -200,7 +200,8 @@
  */
 public class EventBus extends BroadcastReceiver {
 
-    public static final String TAG = "EventBus";
+    private static final String TAG = "EventBus";
+    private static final boolean DEBUG_TRACE_ALL = false;
 
     /**
      * An event super class that allows us to track internal event state across subscriber
@@ -277,9 +278,6 @@
     // The default priority of all subscribers
     private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1;
 
-    // Used for debugging everything
-    private static final boolean DEBUG_TRACE_ALL = false;
-
     // Orders the handlers by priority and registration time
     private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
new file mode 100644
index 0000000..f7b2706
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the user taps on the Overview button to iterate to the next item in the
+ * Recents list.
+ */
+public class IterateRecentsEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
index 12e5d3d..968890a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
@@ -23,12 +23,12 @@
 /**
  * This is sent when a {@link TaskView} has been dismissed.
  */
-public class DismissTaskEvent extends EventBus.Event {
+public class DismissTaskViewEvent extends EventBus.Event {
 
     public final Task task;
     public final TaskView taskView;
 
-    public DismissTaskEvent(Task task, TaskView taskView) {
+    public DismissTaskViewEvent(Task task, TaskView taskView) {
         this.task = task;
         this.taskView = taskView;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
new file mode 100644
index 0000000..9f3e9d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Dismisses the currently focused task view.
+ */
+public class DismissFocusedTaskViewEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
new file mode 100644
index 0000000..171ab5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Focuses the next task view in the stack.
+ */
+public class FocusNextTaskViewEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
new file mode 100644
index 0000000..22469e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Focuses the previous task view in the stack.
+ */
+public class FocusPreviousTaskViewEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
index 735f79f..336d2db 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -19,8 +19,8 @@
 import android.os.Handler;
 
 /**
- * A dozer is a class that fires a trigger after it falls asleep.  You can occasionally poke it to
- * wake it up, but it will fall asleep if left untouched.
+ * A dozer is a class that fires a trigger after it falls asleep.
+ * You can occasionally poke the trigger to wake it up, but it will fall asleep if left untouched.
  */
 public class DozeTrigger {
 
@@ -28,7 +28,7 @@
 
     boolean mIsDozing;
     boolean mHasTriggered;
-    int mDozeDurationSeconds;
+    int mDozeDurationMilliseconds;
     Runnable mSleepRunnable;
 
     // Sleep-runnable
@@ -41,9 +41,9 @@
         }
     };
 
-    public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {
+    public DozeTrigger(int dozeDurationMilliseconds, Runnable sleepRunnable) {
         mHandler = new Handler();
-        mDozeDurationSeconds = dozeDurationSeconds;
+        mDozeDurationMilliseconds = dozeDurationMilliseconds;
         mSleepRunnable = sleepRunnable;
     }
 
@@ -69,7 +69,7 @@
     /** Poke this dozer to wake it up for a little bit. */
     void forcePoke() {
         mHandler.removeCallbacks(mDozeRunnable);
-        mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
+        mHandler.postDelayed(mDozeRunnable, mDozeDurationMilliseconds);
         mIsDozing = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
index 8e85bfc..ea6821f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
@@ -48,6 +48,7 @@
 
     float[] xp;
     float[] px;
+    float mLength;
 
     CurveFunction mFn;
     ParametricCurveFunction mScaleFn;
@@ -79,6 +80,7 @@
             dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step);
             pLength += dx[xStep];
         }
+        mLength = pLength;
         // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
         float p = 0;
         px[0] = 0f;
@@ -260,4 +262,11 @@
         }
         return 1f - xToP(maxX, bounds);
     }
+
+    /**
+     * Returns the length of this curve.
+     */
+    public float getArcLength() {
+        return mLength;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index a51e475..141562c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -247,8 +247,10 @@
                 return true;
             }
 
+            // Note, this is only valid because we currently only allow the recents and home
+            // activities in the home stack
             if (isHomeTopMost != null) {
-                isHomeTopMost.value = isInHomeStack(topTask.id);
+                isHomeTopMost.value = SystemServicesProxy.isHomeStack(topTask.stackId);
             }
         }
         return false;
@@ -266,17 +268,6 @@
         return null;
     }
 
-    /** Allow a task to resize. */
-    public void setTaskResizeable(int taskId) {
-        if (mIam == null) return;
-
-        try {
-            mIam.setTaskResizeable(taskId, true);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-    }
-
     /**
      * Resizes the given task to the new bounds.
      */
@@ -290,12 +281,14 @@
         }
     }
 
-    /** Docks a task to the side of the screen. */
-    public void dockTask(int taskId, int createMode) {
+    /** Docks a task to the side of the screen and starts it. */
+    public void startTaskInDockedMode(int taskId, int createMode) {
         if (mIam == null) return;
 
         try {
-            mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, null);
+            final ActivityOptions options = ActivityOptions.makeBasic();
+            options.setDockCreateMode(createMode);
+            mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, options.toBundle());
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -313,16 +306,18 @@
         }
     }
 
-    /** Returns whether the specified task is in the home stack */
-    public boolean isInHomeStack(int taskId) {
-        if (mAm == null) return false;
+    /**
+     * Returns whether the given stack id is the home stack id.
+     */
+    public static boolean isHomeStack(int stackId) {
+        return stackId == ActivityManager.HOME_STACK_ID;
+    }
 
-        // If we are mocking, then just return false
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
-            return false;
-        }
-
-        return mAm.isInHomeStack(taskId);
+    /**
+     * Returns whether the given stack id is the freeform workspace stack id.
+     */
+    public static boolean isFreeformStack(int stackId) {
+        return stackId == ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 8de8e15..6fc4e20 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -41,6 +41,7 @@
  *      options specified, such that we can transition into the Recents activity seamlessly
  */
 public class RecentsTaskLoadPlan {
+
     private static String TAG = "RecentsTaskLoadPlan";
     private static boolean DEBUG = false;
 
@@ -58,39 +59,50 @@
     }
 
     Context mContext;
-    RecentsConfiguration mConfig;
 
     List<ActivityManager.RecentTaskInfo> mRawTasks;
     TaskStack mStack;
 
     /** Package level ctor */
-    RecentsTaskLoadPlan(Context context, RecentsConfiguration config) {
+    RecentsTaskLoadPlan(Context context) {
         mContext = context;
-        mConfig = config;
     }
 
     /**
-     * An optimization to preload the raw list of tasks.
+     * An optimization to preload the raw list of tasks.  The raw tasks are saved in least-recent
+     * to most-recent order.
      */
     public synchronized void preloadRawTasks(boolean isTopTaskHome) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
                 UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
+        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
         Collections.reverse(mRawTasks);
 
-        if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
+        if (DEBUG) {
+            Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
+            for (ActivityManager.RecentTaskInfo info : mRawTasks) {
+                Log.d(TAG, "  " + info.baseIntent + ", " + info.lastActiveTime);
+            }
+        }
     }
 
     /**
      * Preloads the list of recent tasks from the system.  After this call, the TaskStack will
      * have a list of all the recent tasks with their metadata, not including icons or
      * thumbnails which were not cached and have to be loaded.
+     *
+     * The tasks will be ordered by:
+     * - least-recent to most-recent stack tasks
+     * - least-recent to most-recent freeform tasks
      */
     public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
         if (DEBUG) Log.d(TAG, "preloadPlan");
 
+        RecentsConfiguration config = Recents.getConfiguration();
         SystemServicesProxy ssp = Recents.getSystemServices();
         Resources res = mContext.getResources();
+        ArrayList<Task> freeformTasks = new ArrayList<>();
         ArrayList<Task> stackTasks = new ArrayList<>();
         if (mRawTasks == null) {
             preloadRawTasks(isTopTaskHome);
@@ -113,16 +125,14 @@
             int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
 
             Bitmap icon = t.taskDescription != null
-                    ? t.taskDescription.getInMemoryIcon()
-                    : null;
+                    ? t.taskDescription.getInMemoryIcon() : null;
             String iconFilename = t.taskDescription != null
-                    ? t.taskDescription.getIconFilename()
-                    : null;
+                    ? t.taskDescription.getIconFilename() : null;
 
             // Add the task to the stack
             Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
                     t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
-                    activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
+                    activityColor, (i == (taskCount - 1)), config.lockToAppEnabled, icon,
                     iconFilename);
             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
             if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
@@ -145,6 +155,7 @@
                 ", # thumbnails: " + opts.numVisibleTaskThumbnails +
                 ", running task id: " + opts.runningTaskId);
 
+        RecentsConfiguration config = Recents.getConfiguration();
         SystemServicesProxy ssp = Recents.getSystemServices();
         Resources res = mContext.getResources();
 
@@ -175,9 +186,9 @@
             if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
                 if (task.thumbnail == null || isRunningTask) {
                     if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
-                    if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+                    if (config.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
                         task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, true);
-                    } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
+                    } else if (config.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
                         loadQueue.addTask(task);
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index ea97b71..71e3957 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -155,7 +155,7 @@
                     }
                 }
             } else {
-                RecentsConfiguration config = RecentsConfiguration.getInstance();
+                RecentsConfiguration config = Recents.getConfiguration();
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 // If we've stopped the loader, then fall through to the above logic to wait on
                 // the load thread
@@ -320,8 +320,7 @@
 
     /** Creates a new plan for loading the recent tasks. */
     public RecentsTaskLoadPlan createLoadPlan(Context context) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
         return plan;
     }
 
@@ -383,7 +382,7 @@
      * out of memory.
      */
     public void onTrimMemory(int level) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        RecentsConfiguration config = Recents.getConfiguration();
         switch (level) {
             case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
                 // Stop the loader immediately when the UI is no longer visible
@@ -528,7 +527,7 @@
         }
 
         if (loadIfNotCached) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            RecentsConfiguration config = Recents.getConfiguration();
             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
                 // Load the thumbnail from the system
                 thumbnail = ssp.getTaskThumbnail(taskKey.id);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 0793180..60051b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -170,6 +170,12 @@
         }
     }
 
+    public boolean isFreeformTask() {
+        // Temporarily disable:
+        return false;
+        // return SystemServicesProxy.isFreeformStack(key.stackId);
+    }
+
     /** Notifies the callback listeners that this task has been loaded */
     public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
         this.applicationIcon = applicationIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index fb05c01..21b6bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -20,13 +20,12 @@
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewOutlineProvider;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 
 /* An outline provider that has a clip and outline that can be animated. */
 public class AnimateableViewBounds extends ViewOutlineProvider {
 
-    RecentsConfiguration mConfig;
-
     TaskView mSourceView;
     Rect mClipRect = new Rect();
     Rect mClipBounds = new Rect();
@@ -35,7 +34,6 @@
     final float mMinAlpha = 0.25f;
 
     public AnimateableViewBounds(TaskView source, int cornerRadius) {
-        mConfig = RecentsConfiguration.getInstance();
         mSourceView = source;
         mCornerRadius = cornerRadius;
         setClipBottom(getClipBottom());
@@ -64,7 +62,9 @@
             mClipRect.bottom = bottom;
             mSourceView.invalidateOutline();
             updateClipBounds();
-            if (!mConfig.useHardwareLayers) {
+
+            RecentsConfiguration config = Recents.getConfiguration();
+            if (!config.useHardwareLayers) {
                 mSourceView.mThumbnailView.updateThumbnailVisibility(
                         bottom - mSourceView.getPaddingBottom());
             }
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 7f618e3c..a8a8259 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -49,7 +49,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -69,6 +69,7 @@
 public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
 
     private static final String TAG = "RecentsView";
+    private static final boolean DEBUG = false;
 
     private static final boolean ADD_HEADER_BITMAP = true;
 
@@ -80,7 +81,6 @@
         public void runAfterPause(Runnable r);
     }
 
-    RecentsConfiguration mConfig;
     LayoutInflater mInflater;
 
     ArrayList<TaskStack> mStacks;
@@ -116,7 +116,6 @@
     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setWillNotDraw(false);
-        mConfig = RecentsConfiguration.getInstance();
         mInflater = LayoutInflater.from(context);
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_slow_in);
@@ -130,7 +129,8 @@
 
     /** Set/get the bsp root node */
     public void setTaskStack(TaskStack stack) {
-        if (mConfig.getLaunchState().launchedReuseTaskStackViews) {
+        RecentsConfiguration config = Recents.getConfiguration();
+        if (config.getLaunchState().launchedReuseTaskStackViews) {
             if (mTaskStackView != null) {
                 // If onRecentsHidden is not triggered, we need to the stack view again here
                 mTaskStackView.reset();
@@ -310,13 +310,14 @@
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        RecentsConfiguration config = Recents.getConfiguration();
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
         // Get the search bar bounds and measure the search bar layout
         Rect searchBarSpaceBounds = new Rect();
         if (mSearchBar != null) {
-            mConfig.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+            config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
                     searchBarSpaceBounds);
             mSearchBar.measure(
                     MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
@@ -324,7 +325,7 @@
         }
 
         Rect taskStackBounds = new Rect();
-        mConfig.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+        config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
                 mSystemInsets.right, searchBarSpaceBounds, taskStackBounds);
         if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
             mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets);
@@ -345,11 +346,13 @@
      */
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        RecentsConfiguration config = Recents.getConfiguration();
+
         // Get the search bar bounds so that we lay it out
         Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
         Rect searchBarSpaceBounds = new Rect();
         if (mSearchBar != null) {
-            mConfig.getSearchBarBounds(measuredRect,
+            config.getSearchBarBounds(measuredRect,
                     mSystemInsets.top, searchBarSpaceBounds);
             mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
                     searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
@@ -404,22 +407,6 @@
         return super.verifyDrawable(who);
     }
 
-    /** Focuses the next task in the first stack view */
-    public void focusNextTask(boolean forward) {
-        // Get the first stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.focusNextTask(forward, true);
-        }
-    }
-
-    /** Dismisses the focused task. */
-    public void dismissFocusedTask() {
-        // Get the first stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.dismissFocusedTask();
-        }
-    }
-
     /** Unfilters any filtered stacks */
     public boolean unfilterFilteredStacks() {
         if (mStacks != null) {
@@ -562,7 +549,7 @@
         // Disable any focused state before we draw the header
         // Upfront the processing of the thumbnail
         if (tv.isFocusedTask()) {
-            tv.unsetFocusedTask();
+            tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
         }
         TaskViewTransform transform = new TaskViewTransform();
         transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
@@ -682,7 +669,7 @@
                     } else {
                         // Dismiss the task and return the user to home if we fail to
                         // launch the task
-                        EventBus.getDefault().send(new DismissTaskEvent(task, tv));
+                        EventBus.getDefault().send(new DismissTaskViewEvent(task, tv));
                         if (mCb != null) {
                             mCb.onTaskLaunchFailed();
                         }
@@ -800,8 +787,7 @@
                 // Dock the new task if we are hovering over a valid dock state
                 if (event.dockState != TaskStack.DockState.NONE) {
                     SystemServicesProxy ssp = Recents.getSystemServices();
-                    ssp.setTaskResizeable(event.task.key.id);
-                    ssp.dockTask(event.task.key.id, event.dockState.createMode);
+                    ssp.startTaskInDockedMode(event.task.key.id, event.dockState.createMode);
                     launchTask(event.task, null, INVALID_STACK_ID);
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index cf4c9cb..f3a4390 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -19,6 +19,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.view.MotionEvent;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
@@ -34,14 +35,16 @@
  */
 class DockRegion {
     public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+            // We only allow docking to the left for now on small devices
             TaskStack.DockState.LEFT
     };
     public static TaskStack.DockState[] PHONE_PORTRAIT = {
-            // We only allow docking to the top for now
+            // We only allow docking to the top for now on small devices
             TaskStack.DockState.TOP
     };
     public static TaskStack.DockState[] TABLET_LANDSCAPE = {
-            TaskStack.DockState.LEFT
+            TaskStack.DockState.LEFT,
+            TaskStack.DockState.RIGHT
     };
     public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
 }
@@ -71,7 +74,7 @@
     public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
         boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
                 Configuration.ORIENTATION_LANDSCAPE;
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        RecentsConfiguration config = Recents.getConfiguration();
         TaskStack.DockState[] dockStates = isLandscape ?
                 (config.isLargeScreen ? DockRegion.TABLET_LANDSCAPE : DockRegion.PHONE_LANDSCAPE) :
                 (config.isLargeScreen ? DockRegion.TABLET_PORTRAIT : DockRegion.PHONE_PORTRAIT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index e04699c1..b7c1de3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -68,6 +68,7 @@
 
     private float mInitialTouchPos;
     private boolean mDragging;
+    private float mSnapBackTranslationX;
 
     private View mCurrView;
     private boolean mCanCurrViewBeDimissed;
@@ -92,6 +93,10 @@
         mDensityScale = densityScale;
     }
 
+    public void setSnapBackTranslationX(float translationX) {
+        mSnapBackTranslationX = translationX;
+    }
+
     public void setPagingTouchSlop(float pagingTouchSlop) {
         mPagingTouchSlop = pagingTouchSlop;
     }
@@ -267,7 +272,7 @@
 
     private void snapChild(final View view, float velocity) {
         final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
-        ValueAnimator anim = createTranslationAnimation(view, 0);
+        ValueAnimator anim = createTranslationAnimation(view, mSnapBackTranslationX);
         int duration = SNAP_ANIM_LEN;
         anim.setDuration(duration);
         anim.setInterpolator(mLinearOutSlowInInterpolator);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 5fbea00..c4e2d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -22,6 +22,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
@@ -31,7 +32,6 @@
 public class SystemBarScrimViews {
 
     Context mContext;
-    RecentsConfiguration mConfig;
 
     View mStatusBarScrimView;
     View mNavBarScrimView;
@@ -48,7 +48,6 @@
 
     public SystemBarScrimViews(Activity activity) {
         mContext = activity;
-        mConfig = RecentsConfiguration.getInstance();
         mStatusBarScrimView = activity.findViewById(R.id.status_bar_scrim);
         mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim);
         mNavBarScrimEnterDuration = activity.getResources().getInteger(
@@ -64,7 +63,8 @@
      * the first draw.
      */
     public void prepareEnterRecentsAnimation() {
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         mHasNavBarScrim = launchState.hasNavBarScrim();
         mShouldAnimateNavBarScrim = launchState.shouldAnimateNavBarScrim();
         mHasStatusBarScrim = launchState.hasStatusBarScrim();
@@ -82,7 +82,8 @@
      * Starts animating the scrim views when entering Recents.
      */
     public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         int transitionEnterFromAppDelay = mContext.getResources().getInteger(
                 R.integer.recents_enter_from_app_transition_duration);
         int transitionEnterFromHomeDelay = mContext.getResources().getInteger(
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 5928854..dfa36d8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -23,13 +23,16 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -37,8 +40,11 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
+import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
@@ -60,6 +66,9 @@
         TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
         ViewPool.ViewPoolConsumer<TaskView, Task> {
 
+    private final static String TAG = "TaskStackView";
+    private final static boolean DEBUG = false;
+
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
         public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
@@ -68,7 +77,6 @@
         public void onTaskStackFilterTriggered();
         public void onTaskStackUnfilterTriggered();
     }
-    RecentsConfiguration mConfig;
 
     TaskStack mStack;
     TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
@@ -80,14 +88,12 @@
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
     DozeTrigger mUIDozeTrigger;
     int mFocusedTaskIndex = -1;
-    int mPrevAccessibilityFocusedIndex = -1;
     // Optimizations
     int mStackViewsAnimationDuration;
     boolean mStackViewsDirty = true;
     boolean mStackViewsClipDirty = true;
     boolean mAwaitingFirstLayout = true;
     boolean mStartEnterAnimationRequestedAfterLayout;
-    boolean mStartEnterAnimationCompleted;
     ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
 
     Rect mTaskStackBounds = new Rect();
@@ -114,10 +120,9 @@
         super(context);
         // Set the stack first
         setStack(stack);
-        mConfig = RecentsConfiguration.getInstance();
         mViewPool = new ViewPool<>(context, this);
         mInflater = LayoutInflater.from(context);
-        mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context, mConfig);
+        mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context);
         mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
         mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
         mStackScroller.setCallbacks(this);
@@ -219,7 +224,6 @@
         mStackViewsDirty = true;
         mStackViewsClipDirty = true;
         mAwaitingFirstLayout = true;
-        mPrevAccessibilityFocusedIndex = -1;
         if (mUIDozeTrigger != null) {
             mUIDozeTrigger.stopDozing();
             mUIDozeTrigger.resetTrigger();
@@ -332,8 +336,6 @@
     /** Synchronizes the views with the model */
     boolean synchronizeStackViewsWithModel() {
         if (mStackViewsDirty) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-
             // Get all the task transforms
             ArrayList<Task> tasks = mStack.getTasks();
             float stackScroll = mStackScroller.getStackScroll();
@@ -344,8 +346,9 @@
             // Return all the invisible children to the pool
             mTmpTaskViewMap.clear();
             List<TaskView> taskViews = getTaskViews();
+            boolean wasLastFocusedTaskAnimated = false;
+            int lastFocusedTaskIndex = -1;
             int taskViewCount = taskViews.size();
-            boolean reaquireAccessibilityFocus = false;
             for (int i = taskViewCount - 1; i >= 0; i--) {
                 TaskView tv = taskViews.get(i);
                 Task task = tv.getTask();
@@ -353,8 +356,12 @@
                 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
                     mTmpTaskViewMap.put(task, tv);
                 } else {
+                    if (tv.isFocusedTask()) {
+                        wasLastFocusedTaskAnimated = tv.isFocusAnimated();
+                        lastFocusedTaskIndex = taskIndex;
+                        resetFocusedTask();
+                    }
                     mViewPool.returnViewToPool(tv);
-                    reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex);
                 }
             }
 
@@ -385,21 +392,14 @@
                 // Animate the task into place
                 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
                         mStackViewsAnimationDuration, mRequestUpdateClippingListener);
+            }
 
-                // Request accessibility focus on the next view if we removed the task
-                // that previously held accessibility focus
-                if (reaquireAccessibilityFocus) {
-                    taskViews = getTaskViews();
-                    taskViewCount = taskViews.size();
-                    if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() &&
-                            mPrevAccessibilityFocusedIndex != -1) {
-                        TaskView atv = taskViews.get(taskViewCount - 1);
-                        int indexOfTask = mStack.indexOfTask(atv.getTask());
-                        if (mPrevAccessibilityFocusedIndex != indexOfTask) {
-                            tv.requestAccessibilityFocus();
-                            mPrevAccessibilityFocusedIndex = indexOfTask;
-                        }
-                    }
+            // Update the focus if the previous focused task was returned to the view pool
+            if (lastFocusedTaskIndex != -1) {
+                if (lastFocusedTaskIndex < visibleRange[1]) {
+                    setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated);
+                } else {
+                    setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated);
                 }
             }
 
@@ -473,118 +473,80 @@
         return mStackScroller;
     }
 
-    /** Focuses the task at the specified index in the stack */
-    void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
-        // Return early if the task is already focused
-        if (taskIndex == mFocusedTaskIndex) return;
+    /**
+     * Sets the focused task to the provided (bounded taskIndex).
+     */
+    private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
+        setFocusedTask(taskIndex, scrollToTask, animated, true);
+    }
 
-        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
-            mFocusedTaskIndex = taskIndex;
-            mPrevAccessibilityFocusedIndex = taskIndex;
+    /**
+     * Sets the focused task to the provided (bounded taskIndex).
+     */
+    private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
+                                final boolean requestViewFocus) {
+        // Find the next task to focus
+        int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
+                Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
+        final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
+                mStack.getTasks().get(newFocusedTaskIndex) : null;
 
-            // Focus the view if possible, otherwise, focus the view after we scroll into position
-            final Task t = mStack.getTasks().get(mFocusedTaskIndex);
-            Runnable postScrollRunnable = new Runnable() {
+        // Reset the last focused task state if changed
+        if (mFocusedTaskIndex != -1) {
+            Task focusedTask = mStack.getTasks().get(mFocusedTaskIndex);
+            if (focusedTask != newFocusedTask) {
+                resetFocusedTask();
+            }
+        }
+
+        mFocusedTaskIndex = newFocusedTaskIndex;
+        if (mFocusedTaskIndex != -1) {
+            Runnable focusTaskRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    TaskView tv = getChildViewForTask(t);
+                    TaskView tv = getChildViewForTask(newFocusedTask);
                     if (tv != null) {
-                        tv.setFocusedTask(animateFocusedState);
-                        tv.requestAccessibilityFocus();
+                        tv.setFocusedState(true, animated, requestViewFocus);
                     }
                 }
             };
 
-            // Scroll the view into position (just center it in the curve)
-            if (scrollToNewPosition) {
-                float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f;
+            if (scrollToTask) {
+                // TODO: Center the newly focused task view
+                float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
                 newScroll = mStackScroller.getBoundedStackScroll(newScroll);
-                mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
+                mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
+                        focusTaskRunnable);
             } else {
-                if (postScrollRunnable != null) {
-                    postScrollRunnable.run();
-                }
+                focusTaskRunnable.run();
             }
-
         }
     }
 
     /**
-     * Ensures that there is a task focused, if nothing is focused, then we will use the task
-     * at the center of the visible stack.
+     * Sets the focused task relative to the currently focused task.
+     *
+     * @param animated determines whether to actually draw the highlight along with the change in
+     *                            focus.
      */
-    public boolean ensureFocusedTask(boolean findClosestToCenter) {
-        if (mFocusedTaskIndex < 0) {
-            List<TaskView> taskViews = getTaskViews();
-            int taskViewCount = taskViews.size();
-            if (findClosestToCenter) {
-                // If there is no task focused, then find the task that is closes to the center
-                // of the screen and use that as the currently focused task
-                int x = mLayoutAlgorithm.mStackRect.centerX();
-                int y = mLayoutAlgorithm.mStackRect.centerY();
-                for (int i = taskViewCount - 1; i >= 0; i--) {
-                    TaskView tv = taskViews.get(i);
-                    tv.getHitRect(mTmpRect);
-                    if (mTmpRect.contains(x, y)) {
-                        mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
-                        mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
-                        break;
-                    }
-                }
-            }
-            // If we can't find the center task, then use the front most index
-            if (mFocusedTaskIndex < 0 && taskViewCount > 0) {
-                TaskView tv = taskViews.get(taskViewCount - 1);
-                mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
-                mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
-            }
-        }
-        return mFocusedTaskIndex >= 0;
-    }
-
-    /**
-     * Focuses the next task in the stack.
-     * @param animateFocusedState determines whether to actually draw the highlight along with
-     *                            the change in focus, as well as whether to scroll to fit the
-     *                            task into view.
-     */
-    public void focusNextTask(boolean forward, boolean animateFocusedState) {
+    public void setRelativeFocusedTask(boolean forward, boolean animated) {
         // Find the next index to focus
-        int numTasks = mStack.getTaskCount();
-        if (numTasks == 0) return;
-
-        int direction = (forward ? -1 : 1);
-        int newIndex = mFocusedTaskIndex + direction;
-        if (newIndex >= 0 && newIndex <= (numTasks - 1)) {
-            newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));
-            focusTask(newIndex, true, animateFocusedState);
-        }
+        int newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+        setFocusedTask(newIndex, true, animated);
     }
 
-    /** Dismisses the focused task. */
-    public void dismissFocusedTask() {
-        // Return early if the focused task index is invalid
-        if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) {
-            mFocusedTaskIndex = -1;
-            return;
-        }
-
-        Task t = mStack.getTasks().get(mFocusedTaskIndex);
-        TaskView tv = getChildViewForTask(t);
-        tv.dismissTask();
-    }
-
-    /** Resets the focused task. */
+    /**
+     * Resets the focused task.
+     */
     void resetFocusedTask() {
-        if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) {
+        if (mFocusedTaskIndex != -1) {
             Task t = mStack.getTasks().get(mFocusedTaskIndex);
             TaskView tv = getChildViewForTask(t);
             if (tv != null) {
-                tv.unsetFocusedTask();
+                tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
             }
         }
         mFocusedTaskIndex = -1;
-        mPrevAccessibilityFocusedIndex = -1;
     }
 
     @Override
@@ -609,12 +571,12 @@
         super.onInitializeAccessibilityNodeInfo(info);
         List<TaskView> taskViews = getTaskViews();
         int taskViewCount = taskViews.size();
-        if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) {
+        if (taskViewCount > 1 && mFocusedTaskIndex != -1) {
             info.setScrollable(true);
-            if (mPrevAccessibilityFocusedIndex > 0) {
+            if (mFocusedTaskIndex > 0) {
                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
             }
-            if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
+            if (mFocusedTaskIndex < mStack.getTaskCount() - 1) {
                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
             }
         }
@@ -630,22 +592,14 @@
         if (super.performAccessibilityAction(action, arguments)) {
             return true;
         }
-        if (ensureFocusedTask(false)) {
-            switch (action) {
-                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
-                    if (mPrevAccessibilityFocusedIndex > 0) {
-                        focusNextTask(true, false);
-                        return true;
-                    }
-                }
-                break;
-                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
-                    if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
-                        focusNextTask(false, false);
-                        return true;
-                    }
-                }
-                break;
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+                setRelativeFocusedTask(true, false /* animated */);
+                return true;
+            }
+            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+                setRelativeFocusedTask(false, false /* animated */);
+                return true;
             }
         }
         return false;
@@ -677,10 +631,9 @@
     }
 
     /** Computes the stack and task rects */
-    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
-            boolean launchedWithAltTab, boolean launchedFromHome) {
+    public void computeRects(Rect taskStackBounds) {
         // Compute the rects in the stack algorithm
-        mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
+        mLayoutAlgorithm.computeRects(taskStackBounds);
 
         // Update the scroll bounds
         updateMinMaxScroll(false);
@@ -718,9 +671,7 @@
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
         // Compute our stack/task rects
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        computeRects(width, height, mTaskStackBounds, launchState.launchedWithAltTab,
-                launchState.launchedFromHome);
+        computeRects(mTaskStackBounds);
 
         // If this is the first layout, then scroll to the front of the stack and synchronize the
         // stack views immediately to load all the views
@@ -741,12 +692,12 @@
                 mTmpRect.setEmpty();
             }
             tv.measure(
-                MeasureSpec.makeMeasureSpec(
-                        mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
-                        MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(
-                        mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
-                        MeasureSpec.EXACTLY));
+                    MeasureSpec.makeMeasureSpec(
+                            mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
+                            MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(
+                            mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
+                            MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -815,17 +766,17 @@
             mStartEnterAnimationContext = null;
         }
 
-        // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the
-        // enter animation).
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        if (launchState.launchedWithAltTab) {
-            if (launchState.launchedFromAppWithThumbnail) {
-                focusTask(Math.max(0, mStack.getTaskCount() - 2), false,
-                        launchState.launchedHasConfigurationChanged);
-            } else {
-                focusTask(Math.max(0, mStack.getTaskCount() - 1), false,
-                        launchState.launchedHasConfigurationChanged);
-            }
+        // Set the task focused state without requesting view focus, and leave the focus animations
+        // until after the enter-animation
+        if (!Constants.DebugFlags.App.EnableFastToggleRecents && launchTargetTask != null) {
+            setFocusedTask(mStack.indexOfTask(launchTargetTask), false /* scrollToTask */,
+                    false /* animated */, false /* requestViewFocus */);
+        } else {
+            RecentsConfiguration config = Recents.getConfiguration();
+            RecentsActivityLaunchState launchState = config.getLaunchState();
+            int taskOffset = launchState.launchedFromHome ? -1 : -2;
+            setFocusedTask(mStack.getTaskCount() + taskOffset, false /* scrollToTask */,
+                    false /* animated */, false /* requestViewFocus */);
         }
 
         // Start dozing
@@ -874,32 +825,18 @@
             ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
                 public void run() {
-                    mStartEnterAnimationCompleted = true;
                     // Poke the dozer to restart the trigger after the animation completes
                     mUIDozeTrigger.poke();
 
-                    SystemServicesProxy ssp = Recents.getSystemServices();
-                    List<TaskView> taskViews = getTaskViews();
-                    int taskViewCount = taskViews.size();
-                    if (taskViewCount > 0) {
-                        // Focus the first view if accessibility is enabled
-                        if (ssp.isTouchExplorationEnabled()) {
-                            TaskView tv = taskViews.get(taskViewCount - 1);
-                            tv.requestAccessibilityFocus();
-                            mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
-                        }
-                    }
-
-                    // Start the focus animation when alt-tabbing
-                    ArrayList<Task> tasks = mStack.getTasks();
-                    RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-                    if (launchState.launchedWithAltTab &&
-                            !launchState.launchedHasConfigurationChanged &&
-                            0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) {
-                        TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex));
-                        if (tv != null) {
-                            tv.setFocusedTask(true);
-                        }
+                    // Update the focused state here -- since we only set the focused task without
+                    // requesting view focus in onFirstLayout(), actually request view focus and
+                    // animate the focused state if we are alt-tabbing now, after the window enter
+                    // animation is completed
+                    if (mFocusedTaskIndex != -1) {
+                        RecentsConfiguration config = Recents.getConfiguration();
+                        RecentsActivityLaunchState launchState = config.getLaunchState();
+                        setFocusedTask(mFocusedTaskIndex, false /* scrollToTask */,
+                                launchState.launchedWithAltTab);
                     }
                 }
             });
@@ -1132,11 +1069,6 @@
     public void prepareViewToEnterPool(TaskView tv) {
         Task task = tv.getTask();
 
-        // Clear the accessibility focus for that view
-        if (tv.isAccessibilityFocused()) {
-            tv.clearAccessibilityFocus();
-        }
-
         // Report that this tasks's data is no longer being used
         Recents.getTaskLoader().unloadTaskData(task);
 
@@ -1167,11 +1099,6 @@
         // If the doze trigger has already fired, then update the state for this task view
         tv.setNoUserInteractionState();
 
-        // If we've finished the start animation, then ensure we always enable the focus animations
-        if (mStartEnterAnimationCompleted) {
-            tv.enableFocusAnimations();
-        }
-
         // Find the index where this task should be placed in the stack
         int insertIndex = -1;
         int taskIndex = mStack.indexOfTask(task);
@@ -1231,13 +1158,6 @@
         }
     }
 
-    @Override
-    public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
-        if (focused) {
-            mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
-        }
-    }
-
     /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
 
     @Override
@@ -1259,13 +1179,13 @@
         for (int i = tasks.size() - 1; i >= 0; i--) {
             final Task t = tasks.get(i);
             if (removedComponents.contains(t.key.getComponent())) {
-                TaskView tv = getChildViewForTask(t);
+                final TaskView tv = getChildViewForTask(t);
                 if (tv != null) {
                     // For visible children, defer removing the task until after the animation
                     tv.startDeleteTaskAnimation(new Runnable() {
                         @Override
                         public void run() {
-                            mStack.removeTask(t);
+                            removeTaskViewFromStack(tv);
                         }
                     }, 0);
                 } else {
@@ -1276,33 +1196,23 @@
         }
     }
 
-    public final void onBusEvent(DismissTaskEvent event) {
-        TaskView tv = event.taskView;
-        Task task = tv.getTask();
-        int taskIndex = mStack.indexOfTask(task);
-        boolean taskWasFocused = tv.isFocusedTask();
+    public final void onBusEvent(DismissTaskViewEvent event) {
+        removeTaskViewFromStack(event.taskView);
+    }
 
-        // Announce for accessibility
-        tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
-                tv.getTask().activityLabel));
+    public final void onBusEvent(FocusNextTaskViewEvent event) {
+        setRelativeFocusedTask(true, true);
+    }
 
-        // Remove the task from the view
-        mStack.removeTask(task);
+    public final void onBusEvent(FocusPreviousTaskViewEvent event) {
+        setRelativeFocusedTask(false, true);
+    }
 
-        // If the dismissed task was focused, then we should focus the new task in the same index
-        if (taskWasFocused) {
-            ArrayList<Task> tasks = mStack.getTasks();
-            int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1);
-            if (nextTaskIndex >= 0) {
-                Task nextTask = tasks.get(nextTaskIndex);
-                TaskView nextTv = getChildViewForTask(nextTask);
-                if (nextTv != null) {
-                    // Focus the next task, and only animate the visible state if we are launched
-                    // from Alt-Tab
-                    RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-                    nextTv.setFocusedTask(launchState.launchedWithAltTab);
-                }
-            }
+    public final void onBusEvent(DismissFocusedTaskViewEvent event) {
+        if (mFocusedTaskIndex != -1) {
+            Task t = mStack.getTasks().get(mFocusedTaskIndex);
+            TaskView tv = getChildViewForTask(t);
+            tv.dismissTask();
         }
     }
 
@@ -1316,4 +1226,33 @@
             reset();
         }
     }
+
+    /**
+     * Removes the task from the stack, and updates the focus to the next task in the stack if the
+     * removed TaskView was focused.
+     */
+    private void removeTaskViewFromStack(TaskView tv) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Task task = tv.getTask();
+        int taskIndex = mStack.indexOfTask(task);
+        boolean taskWasFocused = tv.isFocusedTask();
+
+        // Reset the previously focused task before it is removed from the stack
+        resetFocusedTask();
+
+        // Announce for accessibility
+        tv.announceForAccessibility(getContext().getString(
+                R.string.accessibility_recents_item_dismissed, tv.getTask().activityLabel));
+
+        // Remove the task from the stack
+        mStack.removeTask(task);
+
+        if (taskWasFocused || ssp.isTouchExplorationEnabled()) {
+            // If the dismissed task was focused or if we are in touch exploration mode, then focus
+            // the next task
+            RecentsConfiguration config = Recents.getConfiguration();
+            RecentsActivityLaunchState launchState = config.getLaunchState();
+            setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 9a5d9bd..c74c654 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.util.Log;
 import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.ParametricCurve;
 import com.android.systemui.recents.misc.Utilities;
@@ -32,14 +33,15 @@
 
 /**
  * The layout logic for a TaskStackView.
+ *
  */
 public class TaskStackViewLayoutAlgorithm {
 
-    private static final boolean DEBUG = false;
     private static final String TAG = "TaskStackViewLayoutAlgorithm";
+    private static final boolean DEBUG = false;
 
     // The min scale of the last task at the top of the curve
-    private static final float STACK_PEEK_MIN_SCALE = 0.75f;
+    private static final float STACK_PEEK_MIN_SCALE = 0.85f;
     // The scale of the last task
     private static final float SINGLE_TASK_SCALE = 0.95f;
     // The percentage of height of task to show between tasks
@@ -58,14 +60,17 @@
     }
 
     Context mContext;
-    RecentsConfiguration mConfig;
 
     // This is the view bounds inset exactly by the search bar, but without the bottom inset
     // see RecentsConfiguration.getTaskStackBounds()
     public Rect mStackRect = new Rect();
+
     // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
     // of the stack rect
     public Rect mTaskRect = new Rect();
+
+    // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
+    public Rect mFreeformRect = new Rect();
     // This is the current system insets
     public Rect mSystemInsets = new Rect();
 
@@ -74,8 +79,13 @@
     // The largest scroll progress, at this value, the front most task will be visible above the
     // navigation bar
     float mMaxScrollP;
+    // The scroll progress at which the stack scroll ends and the overscroll begins.  This serves
+    // as the point at which we can show the freeform space.
+    float mMaxStackScrollP;
     // The initial progress that the scroller is set
     float mInitialScrollP;
+    // The task progress for the front-most task in the stack
+    float mFrontMostTaskP;
 
     // The relative progress to ensure that the height between affiliated tasks is respected
     float mWithinAffiliationPOffset;
@@ -89,11 +99,21 @@
     // The relative progress to ensure that the offset from the bottom of the stack to the bottom
     // of the task is respected
     float mTaskBottomPOffset;
+    // The relative progress to ensure that the freeform workspace height is respected
+    float mFreeformWorkspacePOffset;
     // The front-most task bottom offset
     int mTaskBottomOffset;
 
-    // The last computed task count
-    int mNumTasks;
+    // The number of cells in the freeform workspace
+    int mFreeformCellXCount;
+    int mFreeformCellYCount;
+    // The width and height of the cells in the freeform workspace
+    int mFreeformCellWidth;
+    int mFreeformCellHeight;
+
+    // The last computed task counts
+    int mNumStackTasks;
+    int mNumFreeformTasks;
     // The min/max z translations
     int mMinTranslationZ;
     int mMaxTranslationZ;
@@ -104,12 +124,11 @@
     // Log function
     static ParametricCurve sCurve;
 
-    public TaskStackViewLayoutAlgorithm(Context context, RecentsConfiguration config) {
+    public TaskStackViewLayoutAlgorithm(Context context) {
         Resources res = context.getResources();
         mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
         mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
         mContext = context;
-        mConfig = config;
         if (sCurve == null) {
             sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
                 // The large the XScale, the longer the flat area of the curve
@@ -153,23 +172,25 @@
     }
 
     /**
-     * Computes the stack and task rects
+     * Computes the stack and task rects.
      */
-    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
-        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * taskStackBounds.width());
+    public void computeRects(Rect taskStackBounds) {
+        RecentsConfiguration config = Recents.getConfiguration();
+        int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
         int heightPadding = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_stack_top_padding);
 
         // Compute the stack rect, inset from the given task stack bounds
         mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
-                taskStackBounds.right - widthPadding, windowHeight);
+                taskStackBounds.right - widthPadding, taskStackBounds.bottom);
         mTaskBottomOffset = mSystemInsets.bottom + heightPadding;
 
         // Compute the task rect, align it to the top-center square in the stack rect
-        int size = Math.min(mStackRect.width(), taskStackBounds.height() - mTaskBottomOffset);
+        int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset);
         int xOffset = (mStackRect.width() - size) / 2;
         mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
                 mStackRect.right - xOffset, mStackRect.top + size);
+        mFreeformRect.set(mTaskRect);
 
         // Compute the progress offsets
         int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
@@ -184,9 +205,12 @@
         mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
                 mStackRect);
         mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect);
+        mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(),
+                mStackRect);
 
         if (DEBUG) {
             Log.d(TAG, "computeRects");
+            Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
             Log.d(TAG, "\tmStackRect: " + mStackRect);
             Log.d(TAG, "\tmTaskRect: " + mTaskRect);
             Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
@@ -222,29 +246,38 @@
 
         // Clear the progress map
         mTaskProgressMap.clear();
-        mNumTasks = tasks.size();
 
         // Return early if we have no tasks
         if (tasks.isEmpty()) {
-            mMinScrollP = mMaxScrollP = 0;
+            mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0;
+            mNumStackTasks = mNumFreeformTasks = 0;
             return;
         }
 
-        // We calculate the progress by taking the progress of the element from the bottom of the
-        // screen
-        if (mNumTasks == 1) {
-            // Just center the task in the visible stack rect
-            mMinScrollP = mMaxScrollP = mInitialScrollP = 0f;
-            mTaskProgressMap.put(tasks.get(0).key, 0f);
-        } else {
-            // Update the tasks from back to front with the new progresses. We set the initial
-            // progress to the progress at which the top of the last task is near the center of the
-            // visible stack rect.
+        // Filter the set of freeform and stack tasks
+        ArrayList<Task> freeformTasks = new ArrayList<>();
+        ArrayList<Task> stackTasks = new ArrayList<>();
+        for (int i = 0; i < tasks.size(); i++) {
+            Task task = tasks.get(i);
+            if (task.isFreeformTask()) {
+                freeformTasks.add(task);
+            } else {
+                stackTasks.add(task);
+            }
+        }
+        mNumStackTasks = stackTasks.size();
+        mNumFreeformTasks = freeformTasks.size();
+
+        // TODO: In the case where there is only freeform tasks, then the scrolls should be set to
+        // zero
+
+        if (!stackTasks.isEmpty()) {
+            // Update the for each task from back to front.
             float pAtBackMostTaskTop = 0;
             float pAtFrontMostTaskTop = pAtBackMostTaskTop;
-            int taskCount = tasks.size();
+            int taskCount = stackTasks.size();
             for (int i = 0; i < taskCount; i++) {
-                Task task = tasks.get(i);
+                Task task = stackTasks.get(i);
                 mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
 
                 if (i < (taskCount - 1)) {
@@ -255,15 +288,47 @@
                 }
             }
 
-            // Set the max scroll progress to the point at which the bottom of the front-most task
-            // is aligned to the bottom of the stack (including nav bar and stack padding)
-            mMaxScrollP = pAtFrontMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
+            mFrontMostTaskP = pAtFrontMostTaskTop;
+            // Set the max scroll progress to the point at which the top of the front-most task
+            // is aligned to the bottom of the stack (offset by nav bar, padding, and task height)
+            mMaxStackScrollP = getBottomAlignedScrollProgress(pAtFrontMostTaskTop,
+                    mTaskBottomPOffset + mTaskHeightPOffset);
             // Basically align the back-most task such that its progress is the same as the top of
-            // the front most task at the max scroll
-            mMinScrollP = pAtBackMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
-            // The offset the inital scroll position to the front of the stack, with half the front
-            // task height visible
-            mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
+            // the front most task at the max stack scroll
+            mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop,
+                    mTaskBottomPOffset + mTaskHeightPOffset);
+        }
+
+        if (!freeformTasks.isEmpty()) {
+            // Calculate the cell width/height depending on the number of freeform tasks
+            mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(mNumFreeformTasks)));
+            mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) mNumFreeformTasks / mFreeformCellXCount));
+            mFreeformCellWidth = mFreeformRect.width() / mFreeformCellXCount;
+            mFreeformCellHeight = mFreeformRect.height() / mFreeformCellYCount;
+
+            // Put each of the tasks in the progress map at a fixed index (does not need to actually
+            // map to a scroll position, just by index)
+            int taskCount = freeformTasks.size();
+            for (int i = 0; i < taskCount; i++) {
+                Task task = freeformTasks.get(i);
+                mTaskProgressMap.put(task.key, (mFrontMostTaskP + 1) + i);
+            }
+
+            // The max scroll includes the freeform workspace offset. As the scroll progress exceeds
+            // mMaxStackScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
+            // workspace will be visible
+            mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset;
+            mInitialScrollP = mMaxScrollP;
+        } else {
+            mMaxScrollP = mMaxStackScrollP;
+            mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - mTaskHalfHeightPOffset);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
+            Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
+            Log.d(TAG, "mMinScrollP: " + mMinScrollP);
+            Log.d(TAG, "mMaxStackScrollP: " + mMaxStackScrollP);
+            Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
         }
     }
 
@@ -272,11 +337,24 @@
      * computeMinMaxScroll() is called first.
      */
     public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
+        // Ensure minimum visibility count
         if (tasks.size() <= 1) {
             return new VisibilityReport(1, 1);
         }
 
-        // Walk backwards in the task stack and count the number of tasks and visible thumbnails
+        // If there are freeform tasks, then they will be the only ones visible
+        int freeformTaskCount = 0;
+        for (Task t : tasks) {
+            if (t.isFreeformTask()) {
+                freeformTaskCount++;
+            }
+        }
+        if (freeformTaskCount > 0) {
+            return new VisibilityReport(freeformTaskCount, freeformTaskCount);
+        }
+
+        // Otherwise, walk backwards in the stack and count the number of tasks and visible
+        // thumbnails
         int taskHeight = mTaskRect.height();
         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_bar_height);
@@ -338,18 +416,38 @@
     /** Update/get the transform */
     public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform prevTransform) {
-        if (DEBUG) {
-            Log.d(TAG, "getStackTransform: " + stackScroll);
+        float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
+        int overscrollYOffset = 0;
+        if (mNumFreeformTasks > 0) {
+            overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height());
         }
 
-        if (mNumTasks == 1) {
+        if ((mNumFreeformTasks > 0) && (stackScroll > mMaxStackScrollP) &&
+                (taskProgress > mFrontMostTaskP)) {
+            // This is a freeform task, so lay it out in the freeform workspace
+            int taskIndex = Math.round(taskProgress - (mFrontMostTaskP + 1));
+            int x = taskIndex % mFreeformCellXCount;
+            int y = taskIndex / mFreeformCellXCount;
+            int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
+            float scale = (float) mFreeformCellWidth / mTaskRect.width();
+            int scaleXOffset = (int) (((1f - scale) * mTaskRect.width()) / 2);
+            int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
+            transformOut.scale = scale;
+            transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
+            transformOut.translationY = frontTaskBottom - overscrollYOffset +
+                    (y * mFreeformCellHeight) - scaleYOffset;
+            transformOut.visible = true;
+            return transformOut;
+
+        } else if (mNumStackTasks == 1) {
             // Center the task in the stack, changing the scale will not follow the curve, but just
             // modulate some values directly
-            float pTaskRelative = -stackScroll;
+            float pTaskRelative = mMinScrollP - stackScroll;
             float scale = SINGLE_TASK_SCALE;
             int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2;
             transformOut.scale = scale;
-            transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
+            transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) -
+                    overscrollYOffset;
             transformOut.translationZ = mMaxTranslationZ;
             transformOut.rect.set(mTaskRect);
             transformOut.rect.offset(0, transformOut.translationY);
@@ -357,8 +455,12 @@
             transformOut.visible = true;
             transformOut.p = pTaskRelative;
             return transformOut;
+
         } else {
             float pTaskRelative = taskProgress - stackScroll;
+            if (mNumFreeformTasks > 0) {
+                pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative);
+            }
             float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
             // If the task top is outside of the bounds below the screen, then immediately reset it
             if (pTaskRelative > 1f) {
@@ -379,7 +481,7 @@
             int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
             transformOut.scale = scale;
             transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
-                    scaleYOffset;
+                    scaleYOffset - overscrollYOffset;
             transformOut.translationZ = Math.max(mMinTranslationZ,
                     mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
             transformOut.rect.set(mTaskRect);
@@ -392,6 +494,27 @@
     }
 
     /**
+     * Update/get the transform
+     */
+    public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
+            TaskViewTransform transformOut) {
+        transformOut.reset();
+        if (mNumFreeformTasks == 0) {
+            return transformOut;
+        }
+
+        if (stackScroll > mMaxStackScrollP) {
+            float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
+            int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height());
+            int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
+            transformOut.visible = true;
+            transformOut.alpha =
+            transformOut.translationY = frontTaskBottom - overscrollYOffset;
+        }
+        return transformOut;
+    }
+
+    /**
      * Returns the untransformed task view bounds.
      */
     public Rect getUntransformedTaskViewBounds() {
@@ -406,4 +529,29 @@
         if (!mTaskProgressMap.containsKey(t.key)) return 0f;
         return mTaskProgressMap.get(t.key);
     }
+
+    /**
+     * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
+     * length of the curve.  We know the curve is mostly flat, so we just map the length of the
+     * screen along the arc-length proportionally (1/arclength).
+     */
+    public float getDeltaPForY(int downY, int y) {
+        float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength());
+        return -deltaP;
+    }
+
+    /**
+     * This is the inverse of {@link #getDeltaPForY}.  Given a movement along the arc length
+     * of the curve, map back to the screen y.
+     */
+    public int getYForDeltaP(float downScrollP, float p) {
+        int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength());
+        return -y;
+    }
+
+    private float getBottomAlignedScrollProgress(float p, float pOffsetFromBottom) {
+        // At scroll progress == p, then p is at the top of the stack
+        // At scroll progress == p + 1, then p is at the bottom of the stack
+        return p - (1 - pOffsetFromBottom);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 3d3b13d..15fcab4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -21,6 +21,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.util.Log;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.OverScroller;
@@ -29,8 +30,12 @@
 
 /* The scrolling logic for a TaskStackView */
 public class TaskStackViewScroller {
+
+    private static final String TAG = "TaskStackViewScroller";
+    private static final boolean DEBUG = false;
+
     public interface TaskStackViewScrollerCallbacks {
-        public void onScrollChanged(float p);
+        void onScrollChanged(float p);
     }
 
     Context mContext;
@@ -38,6 +43,8 @@
     TaskStackViewScrollerCallbacks mCb;
 
     float mStackScrollP;
+    float mFlingDownScrollP;
+    int mFlingDownY;
 
     OverScroller mScroller;
     ObjectAnimator mScrollAnimator;
@@ -77,11 +84,6 @@
         }
     }
 
-    /** Sets the current stack scroll without calling the callback. */
-    void setStackScrollRaw(float s) {
-        mStackScrollP = s;
-    }
-
     /**
      * Sets the current stack scroll to the initial state when you first enter recents.
      * @return whether the stack progress changed.
@@ -92,6 +94,20 @@
         return Float.compare(prevStackScrollP, mStackScrollP) != 0;
     }
 
+    /**
+     * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}.
+     */
+    public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY,
+            int overscroll) {
+        if (DEBUG) {
+            Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y +
+                    ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY);
+        }
+        mFlingDownScrollP = downScrollP;
+        mFlingDownY = downY;
+        mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll);
+    }
+
     /** Bounds the current scroll if necessary */
     public boolean boundScroll() {
         float curScroll = getStackScroll();
@@ -174,21 +190,20 @@
 
     /**** OverScroller ****/
 
+    // TODO: Remove
+    @Deprecated
     int progressToScrollRange(float p) {
         return (int) (p * mLayoutAlgorithm.mStackRect.height());
     }
 
-    float scrollRangeToProgress(int s) {
-        return (float) s / mLayoutAlgorithm.mStackRect.height();
-    }
-
     /** Called from the view draw, computes the next scroll. */
     boolean computeScroll() {
         if (mScroller.computeScrollOffset()) {
-            float scroll = scrollRangeToProgress(mScroller.getCurrY());
-            setStackScrollRaw(scroll);
-            if (mCb != null) {
-                mCb.onScrollChanged(scroll);
+            float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY());
+            float scroll = mFlingDownScrollP + deltaP;
+            setStackScroll(scroll);
+            if (DEBUG) {
+                Log.d(TAG, "computeScroll: " + scroll);
             }
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 1274318..08889c5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -17,6 +17,7 @@
 package com.android.systemui.recents.views;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -28,13 +29,17 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 
 import java.util.List;
 
 /* Handles touch events for a TaskStackView. */
 class TaskStackViewTouchHandler implements SwipeHelper.Callback {
-    static int INACTIVE_POINTER_ID = -1;
+
+    private static final String TAG = "TaskStackViewTouchHandler";
+    private static final boolean DEBUG = true;
+
+    private static int INACTIVE_POINTER_ID = -1;
 
     Context mContext;
     TaskStackView mSv;
@@ -42,21 +47,16 @@
     VelocityTracker mVelocityTracker;
 
     boolean mIsScrolling;
-
-    float mInitialP;
-    float mLastP;
-    float mTotalPMotion;
-    int mInitialMotionX, mInitialMotionY;
-    int mLastMotionX, mLastMotionY;
+    float mDownScrollP;
+    int mDownX, mDownY;
     int mActivePointerId = INACTIVE_POINTER_ID;
+    int mOverscrollSize;
     TaskView mActiveTaskView = null;
 
     int mMinimumVelocity;
     int mMaximumVelocity;
     // The scroll touch slop is used to calculate when we start scrolling
     int mScrollTouchSlop;
-    // The page touch slop is used to calculate when we start swiping
-    float mPagingTouchSlop;
     // Used to calculate when a tap is outside a task view rectangle.
     final int mWindowTouchSlop;
 
@@ -65,18 +65,20 @@
 
     public TaskStackViewTouchHandler(Context context, TaskStackView sv,
             TaskStackViewScroller scroller) {
-        mContext = context;
+        Resources res = context.getResources();
         ViewConfiguration configuration = ViewConfiguration.get(context);
+        mContext = context;
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
         mScrollTouchSlop = configuration.getScaledTouchSlop();
-        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
         mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
         mSv = sv;
         mScroller = scroller;
 
-        float densityScale = context.getResources().getDisplayMetrics().density;
-        mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, mPagingTouchSlop);
+        float densityScale = res.getDisplayMetrics().density;
+        mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
+        mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale,
+                configuration.getScaledPagingTouchSlop());
         mSwipeHelper.setMinAlpha(1f);
     }
 
@@ -88,11 +90,6 @@
             mVelocityTracker.clear();
         }
     }
-    void initVelocityTrackerIfNotExists() {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-    }
     void recycleVelocityTracker() {
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
@@ -115,175 +112,70 @@
         return null;
     }
 
-    /** Constructs a simulated motion event for the current stack scroll. */
-    MotionEvent createMotionEventForStackScroll(MotionEvent ev) {
-        MotionEvent pev = MotionEvent.obtainNoHistory(ev);
-        pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));
-        return pev;
-    }
-
     /** Touch preprocessing for handling below */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // Return early if we have no children
-        boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
-        if (!hasTaskViews) {
-            return false;
-        }
-
         // Pass through to swipe helper if we are swiping
         mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
         if (mInterceptedBySwipeHelper) {
             return true;
         }
 
-        TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
-        boolean wasScrolling = mScroller.isScrolling() ||
-                (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
-        int action = ev.getAction();
-        switch (action & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN: {
-                // Save the touch down info
-                mInitialMotionX = mLastMotionX = (int) ev.getX();
-                mInitialMotionY = mLastMotionY = (int) ev.getY();
-                mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
-                        layoutAlgorithm.mStackRect);
-                mActivePointerId = ev.getPointerId(0);
-                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
-                // Stop the current scroll if it is still flinging
-                mScroller.stopScroller();
-                mScroller.stopBoundScrollAnimation();
-                // Initialize the velocity tracker
-                initOrResetVelocityTracker();
-                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-                break;
-            }
-            case MotionEvent.ACTION_POINTER_DOWN: {
-                final int index = ev.getActionIndex();
-                mActivePointerId = ev.getPointerId(index);
-                mLastMotionX = (int) ev.getX(index);
-                mLastMotionY = (int) ev.getY(index);
-                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
-                // Initialize the velocity tracker if necessary
-                initVelocityTrackerIfNotExists();
-                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-
-                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                int y = (int) ev.getY(activePointerIndex);
-                int x = (int) ev.getX(activePointerIndex);
-                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
-                    // Save the touch move info
-                    mIsScrolling = true;
-                    // Disallow parents from intercepting touch events
-                    final ViewParent parent = mSv.getParent();
-                    if (parent != null) {
-                        parent.requestDisallowInterceptTouchEvent(true);
-                    }
-                }
-
-                mLastMotionX = x;
-                mLastMotionY = y;
-                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
-                break;
-            }
-            case MotionEvent.ACTION_POINTER_UP: {
-                int pointerIndex = ev.getActionIndex();
-                int pointerId = ev.getPointerId(pointerIndex);
-                if (pointerId == mActivePointerId) {
-                    // Select a new active pointer id and reset the motion state
-                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
-                    mActivePointerId = ev.getPointerId(newPointerIndex);
-                    mLastMotionX = (int) ev.getX(newPointerIndex);
-                    mLastMotionY = (int) ev.getY(newPointerIndex);
-                    mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
-                    mVelocityTracker.clear();
-                }
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP: {
-                // Animate the scroll back if we've cancelled
-                mScroller.animateBoundScroll();
-                // Reset the drag state and the velocity tracker
-                mIsScrolling = false;
-                mActivePointerId = INACTIVE_POINTER_ID;
-                mActiveTaskView = null;
-                mTotalPMotion = 0;
-                recycleVelocityTracker();
-                break;
-            }
-        }
-
-        return wasScrolling || mIsScrolling;
+        return handleTouchEvent(ev);
     }
 
     /** Handles touch events once we have intercepted them */
     public boolean onTouchEvent(MotionEvent ev) {
-        // Short circuit if we have no children
-        boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
-        if (!hasTaskViews) {
-            return false;
-        }
-
         // Pass through to swipe helper if we are swiping
         if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
             return true;
         }
 
-        // Update the velocity tracker
-        initVelocityTrackerIfNotExists();
+        handleTouchEvent(ev);
+        return true;
+    }
+
+    private boolean handleTouchEvent(MotionEvent ev) {
+        // Short circuit if we have no children
+        if (mSv.getTaskViews().size() == 0) {
+            return false;
+        }
 
         TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
         int action = ev.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 // Save the touch down info
-                mInitialMotionX = mLastMotionX = (int) ev.getX();
-                mInitialMotionY = mLastMotionY = (int) ev.getY();
-                mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
-                        layoutAlgorithm.mStackRect);
+                mDownX = (int) ev.getX();
+                mDownY = (int) ev.getY();
+                mDownScrollP = mScroller.getStackScroll();
                 mActivePointerId = ev.getPointerId(0);
-                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+                mActiveTaskView = findViewAtPoint(mDownX, mDownY);
+
                 // Stop the current scroll if it is still flinging
                 mScroller.stopScroller();
                 mScroller.stopBoundScrollAnimation();
+
                 // Initialize the velocity tracker
                 initOrResetVelocityTracker();
-                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-                // Disallow parents from intercepting touch events
-                final ViewParent parent = mSv.getParent();
-                if (parent != null) {
-                    parent.requestDisallowInterceptTouchEvent(true);
-                }
+                mVelocityTracker.addMovement(ev);
                 break;
             }
             case MotionEvent.ACTION_POINTER_DOWN: {
                 final int index = ev.getActionIndex();
+                mDownX = (int) ev.getX();
+                mDownY = (int) ev.getY();
+                mDownScrollP = mScroller.getStackScroll();
                 mActivePointerId = ev.getPointerId(index);
-                mLastMotionX = (int) ev.getX(index);
-                mLastMotionY = (int) ev.getY(index);
-                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
-                        layoutAlgorithm.mStackRect);
+                mVelocityTracker.addMovement(ev);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
-                if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
-                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-
                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                int x = (int) ev.getX(activePointerIndex);
                 int y = (int) ev.getY(activePointerIndex);
-                int yTotal = Math.abs(y - mInitialMotionY);
-                float curP = layoutAlgorithm.sCurve.xToP(y, layoutAlgorithm.mStackRect);
-                float deltaP = mLastP - curP;
                 if (!mIsScrolling) {
-                    if (yTotal > mScrollTouchSlop) {
+                    if (Math.abs(y - mDownY) > mScrollTouchSlop) {
                         mIsScrolling = true;
+
                         // Disallow parents from intercepting touch events
                         final ViewParent parent = mSv.getParent();
                         if (parent != null) {
@@ -292,54 +184,14 @@
                     }
                 }
                 if (mIsScrolling) {
-                    float curStackScroll = mScroller.getStackScroll();
-                    float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);
-                    if (Float.compare(overScrollAmount, 0f) != 0) {
-                        // Bound the overscroll to a fixed amount, and inversely scale the y-movement
-                        // relative to how close we are to the max overscroll
-                        float maxOverScroll = mContext.getResources().getFloat(
-                                R.dimen.recents_stack_overscroll_percentage);
-                        deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
-                                / maxOverScroll));
-                    }
-                    mScroller.setStackScroll(curStackScroll + deltaP);
-                }
-                mLastMotionX = x;
-                mLastMotionY = y;
-                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
-                mTotalPMotion += Math.abs(deltaP);
-                break;
-            }
-            case MotionEvent.ACTION_UP: {
-                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
-                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
-                    float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
-                    int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
-                            (Constants.Values.TaskStackView.TaskStackMaxOverscrollRange -
-                                    Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
-                    mScroller.mScroller.fling(0,
-                            mScroller.progressToScrollRange(mScroller.getStackScroll()),
-                            0, velocity,
-                            0, 0,
-                            mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP),
-                            mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP),
-                            0, Constants.Values.TaskStackView.TaskStackMinOverscrollRange +
-                                    overscrollRange);
-                    // Invalidate to kick off computeScroll
-                    mSv.invalidate();
-                } else if (mIsScrolling && mScroller.isScrollOutOfBounds()) {
-                    // Animate the scroll back into bounds
-                    mScroller.animateBoundScroll();
-                } else if (mActiveTaskView == null) {
-                    // This tap didn't start on a task.
-                    maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
+                    // If we just move linearly on the screen, then that would map to 1/arclength
+                    // of the curve, so just move the scroll proportional to that
+                    float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
+                    float curScrollP = mDownScrollP + deltaP;
+                    mScroller.setStackScroll(curScrollP);
                 }
 
-                mActivePointerId = INACTIVE_POINTER_ID;
-                mIsScrolling = false;
-                mTotalPMotion = 0;
-                recycleVelocityTracker();
+                mVelocityTracker.addMovement(ev);
                 break;
             }
             case MotionEvent.ACTION_POINTER_UP: {
@@ -349,11 +201,41 @@
                     // Select a new active pointer id and reset the motion state
                     final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
                     mActivePointerId = ev.getPointerId(newPointerIndex);
-                    mLastMotionX = (int) ev.getX(newPointerIndex);
-                    mLastMotionY = (int) ev.getY(newPointerIndex);
-                    mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
-                    mVelocityTracker.clear();
                 }
+                mVelocityTracker.addMovement(ev);
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                mVelocityTracker.addMovement(ev);
+                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+                int y = (int) ev.getY(activePointerIndex);
+                int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+                if (mIsScrolling) {
+                    if (mScroller.isScrollOutOfBounds()) {
+                        // Animate the scroll back into bounds
+                        mScroller.animateBoundScroll();
+                    } else if (Math.abs(velocity) > mMinimumVelocity) {
+                        float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
+                        float curScrollP = mDownScrollP + deltaP;
+                        float downToCurY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+                                curScrollP);
+                        float downToMinY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+                                layoutAlgorithm.mMaxScrollP);
+                        float downToMaxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+                                layoutAlgorithm.mMinScrollP);
+                        mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity,
+                                (int) downToMinY, (int) downToMaxY, mOverscrollSize);
+                        mSv.invalidate();
+                    }
+                } else if (mActiveTaskView == null) {
+                    // This tap didn't start on a task.
+                    maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
+                }
+
+                mActivePointerId = INACTIVE_POINTER_ID;
+                mIsScrolling = false;
+                recycleVelocityTracker();
                 break;
             }
             case MotionEvent.ACTION_CANCEL: {
@@ -363,20 +245,19 @@
                 }
                 mActivePointerId = INACTIVE_POINTER_ID;
                 mIsScrolling = false;
-                mTotalPMotion = 0;
                 recycleVelocityTracker();
                 break;
             }
         }
-        return true;
+        return mIsScrolling;
     }
 
     /** Hides recents if the up event at (x, y) is a tap on the background area. */
     void maybeHideRecentsFromBackgroundTap(int x, int y) {
         // Ignore the up event if it's too far from its start position. The user might have been
         // trying to scroll or swipe.
-        int dx = Math.abs(mInitialMotionX - x);
-        int dy = Math.abs(mInitialMotionY - y);
+        int dx = Math.abs(mDownX - x);
+        int dy = Math.abs(mDownY - y);
         if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
             return;
         }
@@ -408,13 +289,9 @@
                     // Find the front most task and scroll the next task to the front
                     float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
                     if (vScroll > 0) {
-                        if (mSv.ensureFocusedTask(true)) {
-                            mSv.focusNextTask(true, false);
-                        }
+                        mSv.setRelativeFocusedTask(true, false /* animated */);
                     } else {
-                        if (mSv.ensureFocusedTask(true)) {
-                            mSv.focusNextTask(false, false);
-                        }
+                        mSv.setRelativeFocusedTask(false, false /* animated */);
                     }
                     return true;
             }
@@ -431,12 +308,16 @@
 
     @Override
     public boolean canChildBeDismissed(View v) {
+        if (v instanceof TaskView) {
+            return !((TaskView) v).getTask().isFreeformTask();
+        }
         return true;
     }
 
     @Override
     public void onBeginDrag(View v) {
         TaskView tv = (TaskView) v;
+        mSwipeHelper.setSnapBackTranslationX(tv.getTranslationX());
         // Disable clipping with the stack while we are swiping
         tv.setClipViewInStack(false);
         // Disallow touch events from this task view
@@ -461,7 +342,7 @@
         // Re-enable touch events from this task view
         tv.setTouchEnabled(true);
         // Remove the task view from the stack
-        EventBus.getDefault().send(new DismissTaskEvent(tv.getTask(), tv));
+        EventBus.getDefault().send(new DismissTaskViewEvent(tv.getTask(), tv));
         // Keep track of deletions by keyboard
         MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
                 Constants.Metrics.DismissSourceSwipeGesture);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index bab4da7..aaacc6c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,8 +29,8 @@
 import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewOutlineProvider;
@@ -40,13 +40,15 @@
 import android.widget.FrameLayout;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -55,15 +57,15 @@
 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
         View.OnClickListener, View.OnLongClickListener {
 
+    private final static String TAG = "TaskView";
+    private final static boolean DEBUG = false;
+
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
         public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
         public void onTaskViewClipStateChanged(TaskView tv);
-        public void onTaskViewFocusChanged(TaskView tv, boolean focused);
     }
 
-    RecentsConfiguration mConfig;
-
     float mTaskProgress;
     ObjectAnimator mTaskProgressAnimator;
     float mMaxDimScale;
@@ -76,6 +78,7 @@
     Task mTask;
     boolean mTaskDataLoaded;
     boolean mIsFocused;
+    boolean mIsFocusAnimated;
     boolean mFocusAnimationsEnabled;
     boolean mClipViewInStack;
     AnimateableViewBounds mViewBounds;
@@ -116,8 +119,8 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        RecentsConfiguration config = Recents.getConfiguration();
         Resources res = context.getResources();
-        mConfig = RecentsConfiguration.getInstance();
         mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f;
         mClipViewInStack = true;
         mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
@@ -130,8 +133,8 @@
                 com.android.internal.R.interpolator.decelerate_quint);
         setTaskProgress(getTaskProgress());
         setDim(getDim());
-        if (mConfig.fakeShadows) {
-            setBackground(new FakeShadowDrawable(res, mConfig));
+        if (config.fakeShadows) {
+            setBackground(new FakeShadowDrawable(res, config));
         }
         setOutlineProvider(mViewBounds);
     }
@@ -219,9 +222,11 @@
 
     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
                                              ValueAnimator.AnimatorUpdateListener updateCallback) {
+        RecentsConfiguration config = Recents.getConfiguration();
+
         // Apply the transform
         toTransform.applyToTaskView(this, duration, mFastOutSlowInInterpolator, false,
-                !mConfig.fakeShadows, updateCallback);
+                !config.fakeShadows, updateCallback);
 
         // Update the task progress
         Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
@@ -272,7 +277,8 @@
      * first layout because the actual animation into recents may take a long time. */
     void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
                                              boolean occludesLaunchTarget, int offscreenY) {
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         int initialDim = getDim();
         if (launchState.launchedHasConfigurationChanged) {
             // Just load the views as-is
@@ -302,7 +308,8 @@
 
     /** Animates this task view as it enters recents */
     void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
         Resources res = mContext.getResources();
         final TaskViewTransform transform = ctx.currentTaskTransform;
         final int transitionEnterFromAppDelay = res.getInteger(
@@ -317,7 +324,6 @@
                 R.integer.recents_task_enter_from_home_stagger_delay);
         final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
                 R.dimen.recents_task_view_affiliate_group_enter_offset);
-        int startDelay = 0;
 
         if (launchState.launchedFromAppWithThumbnail) {
             if (mTask.isLaunchTarget) {
@@ -366,7 +372,6 @@
                     ctx.postAnimationTrigger.increment();
                 }
             }
-            startDelay = transitionEnterFromAppDelay;
 
         } else if (launchState.launchedFromHome) {
             // Animate the tasks up
@@ -376,7 +381,7 @@
 
             setScaleX(transform.scale);
             setScaleY(transform.scale);
-            if (!mConfig.fakeShadows) {
+            if (!config.fakeShadows) {
                 animate().translationZ(transform.translationZ);
             }
             animate()
@@ -395,17 +400,7 @@
                     })
                     .start();
             ctx.postAnimationTrigger.increment();
-            startDelay = delay;
         }
-
-        // Enable the focus animations from this point onwards so that they aren't affected by the
-        // window transitions
-        postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                enableFocusAnimations();
-            }
-        }, startDelay);
     }
 
     public void fadeInActionButton(int delay, int duration) {
@@ -547,7 +542,7 @@
         startDeleteTaskAnimation(new Runnable() {
             @Override
             public void run() {
-                EventBus.getDefault().send(new DismissTaskEvent(mTask, tv));
+                EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv));
             }
         }, 0);
     }
@@ -584,8 +579,10 @@
 
     /** Returns the current dim. */
     public void setDim(int dim) {
+        RecentsConfiguration config = Recents.getConfiguration();
+
         mDimAlpha = dim;
-        if (mConfig.useHardwareLayers) {
+        if (config.useHardwareLayers) {
             // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
             if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
                 mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
@@ -620,6 +617,8 @@
                 anim.addListener(postAnimRunnable);
             }
             anim.start();
+        } else {
+            postAnimRunnable.onAnimationEnd(null);
         }
     }
 
@@ -641,58 +640,32 @@
     /**** View focus state ****/
 
     /**
-     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
-     * if the view is not currently visible, or we are in touch state (where we still want to keep
-     * track of focus).
+     * Explicitly sets the focused state of this task.
      */
-    public void setFocusedTask(boolean animateFocusedState) {
-        mIsFocused = true;
-        if (mFocusAnimationsEnabled) {
-            // Focus the header bar
-            mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
-        }
-        // Update the thumbnail alpha with the focus
-        mThumbnailView.onFocusChanged(true);
-        // Call the callback
-        if (mCb != null) {
-            mCb.onTaskViewFocusChanged(this, true);
-        }
-        // Workaround, we don't always want it focusable in touch mode, but we want the first task
-        // to be focused after the enter-recents animation, which can be triggered from either touch
-        // or keyboard
-        setFocusableInTouchMode(true);
-        requestFocus();
-        setFocusableInTouchMode(false);
-        invalidate();
-    }
-
-    /**
-     * Unsets the focused task explicitly.
-     */
-    void unsetFocusedTask() {
-        mIsFocused = false;
-        if (mFocusAnimationsEnabled) {
-            // Un-focus the header bar
-            mHeaderView.onTaskViewFocusChanged(false, true);
+    public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) {
+        if (DEBUG) {
+            Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused +
+                    " mIsFocused: " + mIsFocused + " animated: " + animated +
+                    " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() +
+                    " isAccessibilityFocused(): " + isAccessibilityFocused());
         }
 
-        // Update the thumbnail alpha with the focus
-        mThumbnailView.onFocusChanged(false);
-        // Call the callback
-        if (mCb != null) {
-            mCb.onTaskViewFocusChanged(this, false);
-        }
-        invalidate();
-    }
-
-    /**
-     * Updates the explicitly focused state when the view focus changes.
-     */
-    @Override
-    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        if (!gainFocus) {
-            unsetFocusedTask();
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        mIsFocused = isFocused;
+        mIsFocusAnimated = animated;
+        mHeaderView.onTaskViewFocusChanged(isFocused, animated);
+        mThumbnailView.onFocusChanged(isFocused);
+        if (isFocused) {
+            if (requestViewFocus && !isFocused()) {
+                requestFocus();
+            }
+            if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
+                requestAccessibilityFocus();
+            }
+        } else {
+            if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
+                clearAccessibilityFocus();
+            }
         }
     }
 
@@ -700,17 +673,14 @@
      * Returns whether we have explicitly been focused.
      */
     public boolean isFocusedTask() {
-        return mIsFocused || isFocused();
+        return mIsFocused;
     }
 
-    /** Enables all focus animations. */
-    void enableFocusAnimations() {
-        boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
-        mFocusAnimationsEnabled = true;
-        if (mIsFocused && !wasFocusAnimationsEnabled) {
-            // Re-notify the header if we were focused and animations were not previously enabled
-            mHeaderView.onTaskViewFocusChanged(true, true);
-        }
+    /**
+     * Returns whether this focused task is animated.
+     */
+    public boolean isFocusAnimated() {
+        return mIsFocusAnimated;
     }
 
     public void disableLayersForOneFrame() {
@@ -734,13 +704,14 @@
 
     @Override
     public void onTaskDataLoaded() {
+        RecentsConfiguration config = Recents.getConfiguration();
         if (mThumbnailView != null && mHeaderView != null) {
             // Bind each of the views to the new task data
             mThumbnailView.rebindToTask(mTask);
             mHeaderView.rebindToTask(mTask);
             // Rebind any listeners
             mActionButtonView.setOnClickListener(this);
-            setOnLongClickListener(mConfig.hasDockedTasks ? null : this);
+            setOnLongClickListener(config.hasDockedTasks ? null : this);
         }
         mTaskDataLoaded = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index e1e07ef..f6353f8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -39,7 +39,6 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
@@ -244,9 +243,8 @@
         mMoveTaskButton.setOnClickListener(this);
 
         // In accessibility, a single click on the focused app info button will show it
-        AccessibilityManager am = (AccessibilityManager) getContext().
-                getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (am != null && am.isEnabled()) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.isTouchExplorationEnabled()) {
             mApplicationIcon.setOnClickListener(this);
         }
     }
@@ -369,9 +367,6 @@
 
     /** Notifies the associated TaskView has been focused. */
     void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
-        // If we are not animating the visible state, just return
-        if (!animateFocusedState) return;
-
         boolean isRunning = false;
         if (mFocusAnimator != null) {
             isRunning = mFocusAnimator.isRunning();
@@ -379,6 +374,9 @@
         }
 
         if (focused) {
+            // If we are not animating the visible state, just return
+            if (!animateFocusedState) return;
+
             int currentColor = mBackgroundColor;
             int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
             int[][] states = new int[][] {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index ec50eb6..174ff33 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -26,6 +26,7 @@
 /* The transform state for a task view */
 public class TaskViewTransform {
     public int startDelay = 0;
+    public int translationX = 0;
     public int translationY = 0;
     public float translationZ = 0;
     public float scale = 1f;
@@ -43,6 +44,7 @@
 
     public TaskViewTransform(TaskViewTransform o) {
         startDelay = o.startDelay;
+        translationX = o.translationX;
         translationY = o.translationY;
         translationZ = o.translationZ;
         scale = o.scale;
@@ -52,9 +54,12 @@
         p = o.p;
     }
 
-    /** Resets the current transform */
+    /**
+     * Resets the current transform.
+     */
     public void reset() {
         startDelay = 0;
+        translationX = 0;
         translationY = 0;
         translationZ = 0;
         scale = 1f;
@@ -71,6 +76,9 @@
     public boolean hasScaleChangedFrom(float v) {
         return (Float.compare(scale, v) != 0);
     }
+    public boolean hasTranslationXChangedFrom(float v) {
+        return (Float.compare(translationX, v) != 0);
+    }
     public boolean hasTranslationYChangedFrom(float v) {
         return (Float.compare(translationY, v) != 0);
     }
@@ -87,6 +95,9 @@
             boolean requiresLayers = false;
 
             // Animate to the final state
+            if (hasTranslationXChangedFrom(v.getTranslationX())) {
+                anim.translationX(translationX);
+            }
             if (hasTranslationYChangedFrom(v.getTranslationY())) {
                 anim.translationY(translationY);
             }
@@ -117,6 +128,9 @@
                     .start();
         } else {
             // Set the changed properties
+            if (hasTranslationXChangedFrom(v.getTranslationX())) {
+                v.setTranslationX(translationX);
+            }
             if (hasTranslationYChangedFrom(v.getTranslationY())) {
                 v.setTranslationY(translationY);
             }
@@ -147,7 +161,8 @@
 
     @Override
     public String toString() {
-        return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ +
+        return "TaskViewTransform delay: " + startDelay +
+                " x: " + translationX + " y: " + translationY + " z: " + translationZ +
                 " scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect +
                 " p: " + p;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 8938669..2c16f81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -217,7 +217,7 @@
 
     private boolean mDeviceProvisioned = false;
 
-    private RecentsComponent mRecents;
+    protected RecentsComponent mRecents;
 
     protected int mZenMode;
 
@@ -1275,20 +1275,22 @@
 
         int maxHeight = mRowMaxHeight;
         final StatusBarNotification sbn = entry.notification;
-        RemoteViews contentView = sbn.getNotification().contentView;
-        RemoteViews bigContentView = sbn.getNotification().bigContentView;
-        RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView;
+        entry.cacheContentViews(mContext, null);
+
+        final RemoteViews contentView = entry.cachedContentView;
+        final RemoteViews bigContentView = entry.cachedBigContentView;
+        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+        final RemoteViews publicContentView = entry.cachedPublicContentView;
 
         if (contentView == null) {
+            Log.v(TAG, "no contentView for: " + sbn.getNotification());
             return false;
         }
 
         if (DEBUG) {
-            Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion);
+            Log.v(TAG, "publicContentView: " + publicContentView);
         }
 
-        Notification publicNotification = sbn.getNotification().publicVersion;
-
         ExpandableNotificationRow row;
 
         // Stash away previous user expansion state so we can restore it at
@@ -1377,9 +1379,9 @@
 
         // now the public version
         View publicViewLocal = null;
-        if (publicNotification != null) {
+        if (publicContentView != null) {
             try {
-                publicViewLocal = publicNotification.contentView.apply(
+                publicViewLocal = publicContentView.apply(
                         sbn.getPackageContext(mContext),
                         contentContainerPublic, mOnClickHandler);
 
@@ -1537,30 +1539,9 @@
             }
 
             if (viableAction != null) {
-                Notification stripped = n.clone();
-                Notification.Builder.stripForDelivery(stripped);
-                stripped.extras.putBoolean("android.rebuild", true);
-                stripped.actions = new Notification.Action[] { viableAction };
-                stripped.extras.putBoolean("android.rebuild.contentView", true);
-                stripped.contentView = null;
-                stripped.extras.putBoolean("android.rebuild.bigView", true);
-                stripped.bigContentView = null;
-                stripped.extras.putBoolean("android.rebuild.hudView", true);
-                stripped.headsUpContentView = null;
-
-                stripped.extras.putParcelable(Notification.EXTRA_LARGE_ICON,
-                        stripped.getLargeIcon());
-                if (SystemProperties.getBoolean("debug.strip_third_line", false)) {
-                    stripped.extras.putCharSequence(Notification.EXTRA_INFO_TEXT, null);
-                    stripped.extras.putCharSequence(Notification.EXTRA_SUMMARY_TEXT, null);
-                }
-
-                Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
-
-                n.actions = rebuilt.actions;
-                n.bigContentView = rebuilt.bigContentView;
-                n.headsUpContentView = rebuilt.headsUpContentView;
-                n.publicVersion = rebuilt.publicVersion;
+                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
+                rebuilder.setActions(viableAction);
+                rebuilder.build(); // will rewrite n
             }
         }
     }
@@ -2034,12 +2015,15 @@
         }
 
         Notification n = notification.getNotification();
-        if (DEBUG) {
-            logUpdate(entry, n);
-        }
-        boolean applyInPlace = shouldApplyInPlace(entry, n);
+
+        boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
         boolean shouldInterrupt = shouldInterrupt(entry, notification);
         boolean alertAgain = alertAgain(entry, n);
+        if (DEBUG) {
+            Log.d(TAG, "applyInPlace=" + applyInPlace
+                    + " shouldInterrupt=" + shouldInterrupt
+                    + " alertAgain=" + alertAgain);
+        }
 
         entry.notification = notification;
         mGroupManager.onEntryUpdated(entry, entry.notification);
@@ -2104,101 +2088,32 @@
     protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt,
             boolean alertAgain);
 
-    private void logUpdate(Entry oldEntry, Notification n) {
-        StatusBarNotification oldNotification = oldEntry.notification;
-        Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
-                + " ongoing=" + oldNotification.isOngoing()
-                + " expanded=" + oldEntry.getContentView()
-                + " contentView=" + oldNotification.getNotification().contentView
-                + " bigContentView=" + oldNotification.getNotification().bigContentView
-                + " publicView=" + oldNotification.getNotification().publicVersion
-                + " rowParent=" + oldEntry.row.getParent());
-        Log.d(TAG, "new notification: when=" + n.when
-                + " ongoing=" + oldNotification.isOngoing()
-                + " contentView=" + n.contentView
-                + " bigContentView=" + n.bigContentView
-                + " publicView=" + n.publicVersion);
-    }
-
-    /**
-     * @return whether we can just reapply the RemoteViews from a notification in-place when it is
-     * updated
-     */
-    private boolean shouldApplyInPlace(Entry entry, Notification n) {
-        StatusBarNotification oldNotification = entry.notification;
-        // XXX: modify when we do something more intelligent with the two content views
-        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
-        final RemoteViews contentView = n.contentView;
-        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
-        final RemoteViews bigContentView = n.bigContentView;
-        final RemoteViews oldHeadsUpContentView
-                = oldNotification.getNotification().headsUpContentView;
-        final RemoteViews headsUpContentView = n.headsUpContentView;
-        final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
-        final RemoteViews oldPublicContentView = oldPublicNotification != null
-                ? oldPublicNotification.contentView : null;
-        final Notification publicNotification = n.publicVersion;
-        final RemoteViews publicContentView = publicNotification != null
-                ? publicNotification.contentView : null;
-        boolean contentsUnchanged = entry.getContentView() != null
-                && contentView.getPackage() != null
-                && oldContentView.getPackage() != null
-                && oldContentView.getPackage().equals(contentView.getPackage())
-                && oldContentView.getLayoutId() == contentView.getLayoutId();
-        // large view may be null
-        boolean bigContentsUnchanged =
-                (entry.getExpandedContentView() == null && bigContentView == null)
-                || ((entry.getExpandedContentView() != null && bigContentView != null)
-                    && bigContentView.getPackage() != null
-                    && oldBigContentView.getPackage() != null
-                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
-                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
-        boolean headsUpContentsUnchanged =
-                (oldHeadsUpContentView == null && headsUpContentView == null)
-                || ((oldHeadsUpContentView != null && headsUpContentView != null)
-                    && headsUpContentView.getPackage() != null
-                    && oldHeadsUpContentView.getPackage() != null
-                    && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
-                    && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
-        boolean publicUnchanged  =
-                (oldPublicContentView == null && publicContentView == null)
-                || ((oldPublicContentView != null && publicContentView != null)
-                        && publicContentView.getPackage() != null
-                        && oldPublicContentView.getPackage() != null
-                        && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
-                        && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
-        return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
-                && publicUnchanged;
-    }
-
-    private void updateNotificationViews(Entry entry, StatusBarNotification notification) {
-        final RemoteViews contentView = notification.getNotification().contentView;
-        final RemoteViews bigContentView = notification.getNotification().bigContentView;
-        final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
-        final Notification publicVersion = notification.getNotification().publicVersion;
-        final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
-                : null;
+    private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
+        final RemoteViews contentView = entry.cachedContentView;
+        final RemoteViews bigContentView = entry.cachedBigContentView;
+        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+        final RemoteViews publicContentView = entry.cachedPublicContentView;
 
         // Reapply the RemoteViews
         contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
         if (bigContentView != null && entry.getExpandedContentView() != null) {
-            bigContentView.reapply(notification.getPackageContext(mContext),
+            bigContentView.reapply(sbn.getPackageContext(mContext),
                     entry.getExpandedContentView(),
                     mOnClickHandler);
         }
         View headsUpChild = entry.getHeadsUpContentView();
         if (headsUpContentView != null && headsUpChild != null) {
-            headsUpContentView.reapply(notification.getPackageContext(mContext),
+            headsUpContentView.reapply(sbn.getPackageContext(mContext),
                     headsUpChild, mOnClickHandler);
         }
         if (publicContentView != null && entry.getPublicContentView() != null) {
-            publicContentView.reapply(notification.getPackageContext(mContext),
+            publicContentView.reapply(sbn.getPackageContext(mContext),
                     entry.getPublicContentView(), mOnClickHandler);
         }
         // update the contentIntent
-        mNotificationClicker.register(entry.row, notification);
+        mNotificationClicker.register(entry.row, sbn);
 
-        entry.row.setStatusBarNotification(notification);
+        entry.row.setStatusBarNotification(sbn);
         entry.row.notifyContentUpdated();
         entry.row.resetHeight();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index aedae52..6a90d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar;
 
 import android.app.Notification;
+import android.content.Context;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -24,6 +25,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.view.View;
+import android.widget.RemoteViews;
 
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -53,6 +55,10 @@
         public boolean legacy; // whether the notification has a legacy, dark background
         public int targetSdk;
         private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
+        public RemoteViews cachedContentView;
+        public RemoteViews cachedBigContentView;
+        public RemoteViews cachedHeadsUpContentView;
+        public RemoteViews cachedPublicContentView;
 
         public Entry(StatusBarNotification n, StatusBarIconView ic) {
             this.key = n.getKey();
@@ -98,6 +104,67 @@
             return row.getPublicLayout().getContractedChild();
         }
 
+        public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
+            boolean cached = false;
+            if (updatedNotification != null) {
+                final Notification.Builder updatedNotificationBuilder
+                        = Notification.Builder.recoverBuilder(ctx, updatedNotification);
+                final RemoteViews newContentView = updatedNotificationBuilder.makeContentView();
+                if (!compareRemoteViews(cachedContentView, newContentView)) {
+                    cachedContentView = newContentView;
+                    cached |= true;
+                }
+                final RemoteViews newBigContentView =
+                        updatedNotificationBuilder.makeBigContentView();
+                if (!compareRemoteViews(cachedBigContentView, newBigContentView)) {
+                    cachedBigContentView = newBigContentView;
+                    cached |= true;
+                }
+                final RemoteViews newHeadsUpContentView =
+                        updatedNotificationBuilder.makeHeadsUpContentView();
+                if (!compareRemoteViews(cachedHeadsUpContentView, newBigContentView)) {
+                    cachedHeadsUpContentView = newHeadsUpContentView;
+                    cached |= true;
+                }
+                final Notification updatedPublicNotification = updatedNotification.publicVersion;
+                final RemoteViews newPubContentView = (updatedPublicNotification != null)
+                        ? Notification.Builder.recoverBuilder(
+                                ctx, updatedPublicNotification).makeContentView()
+                        : null;
+                if (!compareRemoteViews(cachedPublicContentView, newPubContentView)) {
+                    cachedPublicContentView = newPubContentView;
+                    cached |= true;
+                }
+            } else {
+                final Notification.Builder builder
+                        = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
+
+                cachedContentView = builder.makeContentView();
+                cachedBigContentView = builder.makeBigContentView();
+                cachedHeadsUpContentView = builder.makeHeadsUpContentView();
+
+                final Notification publicNotification =
+                        notification.getNotification().publicVersion;
+                if (publicNotification != null) {
+                    final Notification.Builder publicBuilder
+                            = Notification.Builder.recoverBuilder(ctx, publicNotification);
+                    cachedPublicContentView = publicBuilder.makeContentView();
+                }
+                cached = true;
+            }
+            return cached;
+        }
+
+        // Returns true if the RemoteViews are the same.
+        private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
+            return (a == null && b == null) ||
+                    (a != null && b != null
+                    && b.getPackage() != null
+                    && a.getPackage() != null
+                    && a.getPackage().equals(b.getPackage())
+                    && a.getLayoutId() == b.getLayoutId());
+        }
+
         public void notifyFullScreenIntentLaunched() {
             lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
         }
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 cfde791..f3658eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -88,7 +89,6 @@
 import android.view.ViewStub;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -146,7 +146,6 @@
 import com.android.systemui.statusbar.policy.FullscreenUserSwitcher;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.KeyButtonView;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
@@ -248,15 +247,25 @@
      * Prudently disable QS and notifications.  */
     private static final boolean ONLY_CORE_APPS;
 
+    /* If true, the device supports freeform window management.
+     * This affects the status bar UI. */
+    private static final boolean FREEFORM_WINDOW_MANAGEMENT;
+
     static {
         boolean onlyCoreApps;
+        boolean freeformWindowManagement;
         try {
-            onlyCoreApps = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
-                    .isOnlyCoreApps();
+            IPackageManager packageManager =
+                    IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+            onlyCoreApps = packageManager.isOnlyCoreApps();
+            freeformWindowManagement = packageManager.hasSystemFeature(
+                    PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
         } catch (RemoteException e) {
             onlyCoreApps = false;
+            freeformWindowManagement = false;
         }
         ONLY_CORE_APPS = onlyCoreApps;
+        FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
     }
 
     PhoneStatusBarPolicy mIconPolicy;
@@ -984,8 +993,8 @@
         if (shelfOverride != DEFAULT) {
             return shelfOverride != 0;
         }
-        // Otherwise default to the build setting.
-        return mContext.getResources().getBoolean(R.bool.config_enableAppShelf);
+        // Otherwise default to the platform feature.
+        return FREEFORM_WINDOW_MANAGEMENT;
     }
 
     private void clearAllNotifications() {
@@ -1115,13 +1124,22 @@
         }
     };
 
-    private long mLastLockToAppLongPress;
-    private View.OnLongClickListener mLongPressBackRecentsListener =
-            new View.OnLongClickListener() {
+    private View.OnLongClickListener mLongPressBackListener = new View.OnLongClickListener() {
         @Override
         public boolean onLongClick(View v) {
-            handleLongPressBackRecents(v);
-            return true;
+            return handleLongPressBack();
+        }
+    };
+
+    private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() {
+
+        @Override
+        public boolean onLongClick(View v) {
+            if (mRecents != null) {
+                mRecents.dockTopTask();
+                return true;
+            }
+            return false;
         }
     };
 
@@ -1170,9 +1188,9 @@
         mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
         mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
         mNavigationBarView.getRecentsButton().setLongClickable(true);
-        mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener);
+        mNavigationBarView.getRecentsButton().setOnLongClickListener(mRecentsLongClickListener);
         mNavigationBarView.getBackButton().setLongClickable(true);
-        mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
+        mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackListener);
         mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
         mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener);
         mAssistManager.onConfigurationChanged();
@@ -4048,7 +4066,7 @@
 
     private void vibrateForCameraGesture() {
         // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
-        mVibrator.vibrate(new long[] { 0, 750L }, -1 /* repeat */);
+        mVibrator.vibrate(new long[]{0, 750L}, -1 /* repeat */);
     }
 
     public void onScreenTurnedOn() {
@@ -4057,58 +4075,22 @@
     }
 
     /**
-     * This handles long-press of both back and recents.  They are
-     * handled together to capture them both being long-pressed
-     * at the same time to exit screen pinning (lock task).
-     *
-     * When accessibility mode is on, only a long-press from recents
-     * is required to exit.
-     *
-     * In all other circumstances we try to pass through long-press events
-     * for Back, so that apps can still use it.  Which can be from two things.
-     * 1) Not currently in screen pinning (lock task).
-     * 2) Back is long-pressed without recents.
+     * Handles long press for back button. This exits screen pinning.
      */
-    private void handleLongPressBackRecents(View v) {
+    private boolean handleLongPressBack() {
         try {
-            boolean sendBackLongPress = false;
             IActivityManager activityManager = ActivityManagerNative.getDefault();
-            boolean isAccessiblityEnabled = mAccessibilityManager.isEnabled();
-            if (activityManager.isInLockTaskMode() && !isAccessiblityEnabled) {
-                long time = System.currentTimeMillis();
-                // If we recently long-pressed the other button then they were
-                // long-pressed 'together'
-                if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
-                    activityManager.stopLockTaskModeOnCurrent();
-                    // When exiting refresh disabled flags.
-                    mNavigationBarView.setDisabledFlags(mDisabled1, true);
-                } else if ((v.getId() == R.id.back)
-                        && !mNavigationBarView.getRecentsButton().isPressed()) {
-                    // If we aren't pressing recents right now then they presses
-                    // won't be together, so send the standard long-press action.
-                    sendBackLongPress = true;
-                }
-                mLastLockToAppLongPress = time;
-            } else {
-                // If this is back still need to handle sending the long-press event.
-                if (v.getId() == R.id.back) {
-                    sendBackLongPress = true;
-                } else if (isAccessiblityEnabled && activityManager.isInLockTaskMode()) {
-                    // When in accessibility mode a long press that is recents (not back)
-                    // should stop lock task.
-                    activityManager.stopLockTaskModeOnCurrent();
-                    // When exiting refresh disabled flags.
-                    mNavigationBarView.setDisabledFlags(mDisabled1, true);
-                }
-            }
-            if (sendBackLongPress) {
-                KeyButtonView keyButtonView = (KeyButtonView) v;
-                keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
-                keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+            if (activityManager.isInLockTaskMode()) {
+                activityManager.stopLockTaskModeOnCurrent();
+
+                // When exiting refresh disabled flags.
+                mNavigationBarView.setDisabledFlags(mDisabled1, true);
+                return true;
             }
         } catch (RemoteException e) {
             Log.d(TAG, "Unable to reach activity manager", e);
         }
+        return false;
     }
 
     // Recents
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index 4ce0933..05e3fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -46,6 +46,7 @@
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QSTile.Host.Callback;
 import com.android.systemui.qs.QSTile.ResourceIcon;
+import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.tiles.IntentTile;
 import com.android.systemui.statusbar.phone.QSTileHost;
@@ -381,7 +382,7 @@
     private static class DraggableTile extends QSTile<QSTile.State>
             implements DropListener {
         private String mSpec;
-        private QSTileView mView;
+        private QSTileBaseView mView;
 
         protected DraggableTile(QSTile.Host host, String tileSpec) {
             super(host);
@@ -390,7 +391,7 @@
         }
 
         @Override
-        public QSTileView createTileView(Context context) {
+        public QSTileBaseView createTileView(Context context) {
             mView = super.createTileView(context);
             return mView;
         }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 08f0952e..ed6fc00 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -95,6 +95,7 @@
     static final boolean DEBUG_VALIDATE = localLOGV || false;
     static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
+    static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
     static final int ALARM_EVENT = 1;
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -144,6 +145,16 @@
      */
     final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
 
+    final static class IdleDispatchEntry {
+        int uid;
+        String pkg;
+        String tag;
+        String op;
+        long elapsedRealtime;
+        long argRealtime;
+    }
+    final ArrayList<IdleDispatchEntry> mAllowWhileIdleDispatches = new ArrayList();
+
     /**
      * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE.
      */
@@ -178,7 +189,7 @@
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY;
-        private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 15*60*1000;
+        private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
 
         // Minimum futurity of a new alarm
@@ -700,6 +711,14 @@
     }
 
     void restorePendingWhileIdleAlarmsLocked() {
+        if (RECORD_DEVICE_IDLE_ALARMS) {
+            IdleDispatchEntry ent = new IdleDispatchEntry();
+            ent.uid = 0;
+            ent.pkg = "FINISH IDLE";
+            ent.elapsedRealtime = SystemClock.elapsedRealtime();
+            mAllowWhileIdleDispatches.add(ent);
+        }
+
         // Bring pending alarms back into the main list.
         if (mPendingWhileIdleAlarms.size() > 0) {
             ArrayList<Alarm> alarms = mPendingWhileIdleAlarms;
@@ -1006,6 +1025,19 @@
             }
         }
 
+        if (RECORD_DEVICE_IDLE_ALARMS) {
+            if ((a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+                IdleDispatchEntry ent = new IdleDispatchEntry();
+                ent.uid = a.uid;
+                ent.pkg = a.operation.getCreatorPackage();
+                ent.tag = a.operation.getTag("");
+                ent.op = "SET";
+                ent.elapsedRealtime = SystemClock.elapsedRealtime();
+                ent.argRealtime = a.whenElapsed;
+                mAllowWhileIdleDispatches.add(ent);
+            }
+        }
+
         int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
                 ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
         if (whichBatch < 0) {
@@ -1028,6 +1060,15 @@
         boolean needRebatch = false;
 
         if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+            if (RECORD_DEVICE_IDLE_ALARMS) {
+                if (mPendingIdleUntil == null) {
+                    IdleDispatchEntry ent = new IdleDispatchEntry();
+                    ent.uid = 0;
+                    ent.pkg = "START IDLE";
+                    ent.elapsedRealtime = SystemClock.elapsedRealtime();
+                    mAllowWhileIdleDispatches.add(ent);
+                }
+            }
             mPendingIdleUntil = a;
             mConstants.updateAllowWhileIdleMinTimeLocked();
             needRebatch = true;
@@ -1400,6 +1441,32 @@
                 }
             }
 
+            if (RECORD_DEVICE_IDLE_ALARMS) {
+                pw.println();
+                pw.println("  Allow while idle dispatches:");
+                for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) {
+                    IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i);
+                    pw.print("    ");
+                    TimeUtils.formatDuration(ent.elapsedRealtime, nowELAPSED, pw);
+                    pw.print(": ");
+                    UserHandle.formatUid(pw, ent.uid);
+                    pw.print(":");
+                    pw.println(ent.pkg);
+                    if (ent.op != null) {
+                        pw.print("      ");
+                        pw.print(ent.op);
+                        pw.print(" / ");
+                        pw.print(ent.tag);
+                        if (ent.argRealtime != 0) {
+                            pw.print(" (");
+                            TimeUtils.formatDuration(ent.argRealtime, nowELAPSED, pw);
+                            pw.print(")");
+                        }
+                        pw.println();
+                    }
+                }
+            }
+
             if (WAKEUP_STATS) {
                 pw.println();
                 pw.println("  Recent Wakeup History:");
@@ -1862,6 +1929,16 @@
                         if (alarm.maxWhenElapsed < minTime) {
                             alarm.maxWhenElapsed = minTime;
                         }
+                        if (RECORD_DEVICE_IDLE_ALARMS) {
+                            IdleDispatchEntry ent = new IdleDispatchEntry();
+                            ent.uid = alarm.uid;
+                            ent.pkg = alarm.operation.getCreatorPackage();
+                            ent.tag = alarm.operation.getTag("");
+                            ent.op = "RESCHEDULE";
+                            ent.elapsedRealtime = nowELAPSED;
+                            ent.argRealtime = lastTime;
+                            mAllowWhileIdleDispatches.add(ent);
+                        }
                         setImplLocked(alarm, true, false);
                         continue;
                     }
@@ -2130,6 +2207,15 @@
                 if (allowWhileIdle) {
                     // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
                     mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+                    if (RECORD_DEVICE_IDLE_ALARMS) {
+                        IdleDispatchEntry ent = new IdleDispatchEntry();
+                        ent.uid = alarm.uid;
+                        ent.pkg = alarm.operation.getCreatorPackage();
+                        ent.tag = alarm.operation.getTag("");
+                        ent.op = "DELIVER";
+                        ent.elapsedRealtime = nowELAPSED;
+                        mAllowWhileIdleDispatches.add(ent);
+                    }
                 }
 
                 final BroadcastStats bs = inflight.mBroadcastStats;
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 2eeaec9..61fe62f 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -19,6 +19,8 @@
 import android.database.ContentObserver;
 import android.os.BatteryStats;
 
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
 import com.android.internal.app.IBatteryStats;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.Light;
@@ -96,7 +98,6 @@
     // discharge stats before the device dies.
     private int mCriticalBatteryLevel;
 
-    private static final int DUMP_MAX_LENGTH = 24 * 1024;
     private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "--unplugged" };
 
     private static final String DUMPSYS_DATA_PATH = "/data/system/";
@@ -106,6 +107,7 @@
 
     private final Context mContext;
     private final IBatteryStats mBatteryStats;
+    BinderService mBinderService;
     private final Handler mHandler;
 
     private final Object mLock = new Object();
@@ -162,7 +164,18 @@
 
         // watch for invalid charger messages if the invalid_charger switch exists
         if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
-            mInvalidChargerObserver.startObserving(
+            UEventObserver invalidChargerObserver = new UEventObserver() {
+                @Override
+                public void onUEvent(UEvent event) {
+                    final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
+                    synchronized (mLock) {
+                        if (mInvalidCharger != invalidCharger) {
+                            mInvalidCharger = invalidCharger;
+                        }
+                    }
+                }
+            };
+            invalidChargerObserver.startObserving(
                     "DEVPATH=/devices/virtual/switch/invalid_charger");
         }
     }
@@ -178,7 +191,8 @@
             // Should never happen.
         }
 
-        publishBinderService("battery", new BinderService());
+        mBinderService = new BinderService();
+        publishBinderService("battery", mBinderService);
         publishLocalService(BatteryManagerInternal.class, new LocalService());
     }
 
@@ -593,7 +607,6 @@
             } catch (NumberFormatException e) {
                 Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
                         durationThresholdString + " or " + dischargeThresholdString);
-                return;
             }
         }
     }
@@ -616,7 +629,133 @@
         }
     }
 
-    private void dumpInternal(PrintWriter pw, String[] args) {
+    class Shell extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            dumpHelp(pw);
+        }
+    }
+
+    static void dumpHelp(PrintWriter pw) {
+        pw.println("Battery service (battery) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  set [ac|usb|wireless|status|level|invalid] <value>");
+        pw.println("    Force a battery property value, freezing battery state.");
+        pw.println("  unplug");
+        pw.println("    Force battery unplugged, freezing battery state.");
+        pw.println("  reset");
+        pw.println("    Unfreeze battery state, returning to current hardware values.");
+    }
+
+    int onShellCommand(Shell shell, String cmd) {
+        if (cmd == null) {
+            return shell.handleDefaultCommands(cmd);
+        }
+        PrintWriter pw = shell.getOutPrintWriter();
+        switch (cmd) {
+            case "unplug": {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                if (!mUpdatesStopped) {
+                    mLastBatteryProps.set(mBatteryProps);
+                }
+                mBatteryProps.chargerAcOnline = false;
+                mBatteryProps.chargerUsbOnline = false;
+                mBatteryProps.chargerWirelessOnline = false;
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    mUpdatesStopped = true;
+                    processValuesLocked(false);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            } break;
+            case "set": {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                final String key = shell.getNextArg();
+                if (key == null) {
+                    pw.println("No property specified");
+                    return -1;
+
+                }
+                final String value = shell.getNextArg();
+                if (value == null) {
+                    pw.println("No value specified");
+                    return -1;
+
+                }
+                try {
+                    if (!mUpdatesStopped) {
+                        mLastBatteryProps.set(mBatteryProps);
+                    }
+                    boolean update = true;
+                    switch (key) {
+                        case "ac":
+                            mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
+                            break;
+                        case "usb":
+                            mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
+                            break;
+                        case "wireless":
+                            mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
+                            break;
+                        case "status":
+                            mBatteryProps.batteryStatus = Integer.parseInt(value);
+                            break;
+                        case "level":
+                            mBatteryProps.batteryLevel = Integer.parseInt(value);
+                            break;
+                        case "invalid":
+                            mInvalidCharger = Integer.parseInt(value);
+                            break;
+                        default:
+                            pw.println("Unknown set option: " + key);
+                            update = false;
+                            break;
+                    }
+                    if (update) {
+                        long ident = Binder.clearCallingIdentity();
+                        try {
+                            mUpdatesStopped = true;
+                            processValuesLocked(false);
+                        } finally {
+                            Binder.restoreCallingIdentity(ident);
+                        }
+                    }
+                } catch (NumberFormatException ex) {
+                    pw.println("Bad value: " + value);
+                    return -1;
+                }
+            } break;
+            case "reset": {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    if (mUpdatesStopped) {
+                        mUpdatesStopped = false;
+                        mBatteryProps.set(mLastBatteryProps);
+                        processValuesLocked(false);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            } break;
+            default:
+                return shell.handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
+    private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mLock) {
             if (args == null || args.length == 0 || "-a".equals(args[0])) {
                 pw.println("Current Battery Service state:");
@@ -635,91 +774,13 @@
                 pw.println("  voltage: " + mBatteryProps.batteryVoltage);
                 pw.println("  temperature: " + mBatteryProps.batteryTemperature);
                 pw.println("  technology: " + mBatteryProps.batteryTechnology);
-
-            } else if ("unplug".equals(args[0])) {
-                if (!mUpdatesStopped) {
-                    mLastBatteryProps.set(mBatteryProps);
-                }
-                mBatteryProps.chargerAcOnline = false;
-                mBatteryProps.chargerUsbOnline = false;
-                mBatteryProps.chargerWirelessOnline = false;
-                long ident = Binder.clearCallingIdentity();
-                try {
-                    mUpdatesStopped = true;
-                    processValuesLocked(false);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-
-            } else if (args.length == 3 && "set".equals(args[0])) {
-                String key = args[1];
-                String value = args[2];
-                try {
-                    if (!mUpdatesStopped) {
-                        mLastBatteryProps.set(mBatteryProps);
-                    }
-                    boolean update = true;
-                    if ("ac".equals(key)) {
-                        mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
-                    } else if ("usb".equals(key)) {
-                        mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
-                    } else if ("wireless".equals(key)) {
-                        mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
-                    } else if ("status".equals(key)) {
-                        mBatteryProps.batteryStatus = Integer.parseInt(value);
-                    } else if ("level".equals(key)) {
-                        mBatteryProps.batteryLevel = Integer.parseInt(value);
-                    } else if ("invalid".equals(key)) {
-                        mInvalidCharger = Integer.parseInt(value);
-                    } else {
-                        pw.println("Unknown set option: " + key);
-                        update = false;
-                    }
-                    if (update) {
-                        long ident = Binder.clearCallingIdentity();
-                        try {
-                            mUpdatesStopped = true;
-                            processValuesLocked(false);
-                        } finally {
-                            Binder.restoreCallingIdentity(ident);
-                        }
-                    }
-                } catch (NumberFormatException ex) {
-                    pw.println("Bad value: " + value);
-                }
-
-            } else if (args.length == 1 && "reset".equals(args[0])) {
-                long ident = Binder.clearCallingIdentity();
-                try {
-                    if (mUpdatesStopped) {
-                        mUpdatesStopped = false;
-                        mBatteryProps.set(mLastBatteryProps);
-                        processValuesLocked(false);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
             } else {
-                pw.println("Dump current battery state, or:");
-                pw.println("  set [ac|usb|wireless|status|level|invalid] <value>");
-                pw.println("  unplug");
-                pw.println("  reset");
+                Shell shell = new Shell();
+                shell.exec(mBinderService, null, fd, null, args, new ResultReceiver(null));
             }
         }
     }
 
-    private final UEventObserver mInvalidChargerObserver = new UEventObserver() {
-        @Override
-        public void onUEvent(UEventObserver.UEvent event) {
-            final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
-            synchronized (mLock) {
-                if (mInvalidCharger != invalidCharger) {
-                    mInvalidCharger = invalidCharger;
-                }
-            }
-        }
-    };
-
     private final class Led {
         private final Light mBatteryLight;
 
@@ -776,8 +837,7 @@
     }
 
     private final class BatteryListener extends IBatteryPropertiesListener.Stub {
-        @Override
-        public void batteryPropertiesChanged(BatteryProperties props) {
+        @Override public void batteryPropertiesChanged(BatteryProperties props) {
             final long identity = Binder.clearCallingIdentity();
             try {
                 BatteryService.this.update(props);
@@ -788,8 +848,7 @@
     }
 
     private final class BinderService extends Binder {
-        @Override
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                     != PackageManager.PERMISSION_GRANTED) {
 
@@ -799,7 +858,12 @@
                 return;
             }
 
-            dumpInternal(pw, args);
+            dumpInternal(fd, pw, args);
+        }
+
+        @Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+            (new Shell()).exec(this, in, out, err, args, resultReceiver);
         }
     }
 
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 496e4df..927b995 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -1500,7 +1500,7 @@
                 mState = STATE_SENSING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
                 EventLogTags.writeDeviceIdle(mState, "step");
-                cancelAlarmLocked();
+                scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
                 cancelLocatingLocked();
                 mAnyMotionDetector.checkForAnyMotion();
                 mNotMoving = false;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 6b34612..1ff13b2 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3824,7 +3824,6 @@
         boolean isPermitted =
                 isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS,
                         Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
-        Log.i(TAG, String.format("getTypesVisibleToCaller: isPermitted? %s", isPermitted));
         return getTypesForCaller(callingUid, userId, isPermitted);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9834757..d5ee5e8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -69,12 +69,14 @@
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.ResultReceiver;
 import android.os.Trace;
 import android.os.TransactionTooLargeException;
 import android.os.WorkSource;
 import android.os.storage.IMountService;
 import android.os.storage.MountServiceInternal;
 import android.os.storage.StorageManager;
+import android.provider.Settings.Global;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionSession;
 import android.util.ArrayMap;
@@ -1189,6 +1191,7 @@
     String mOrigDebugApp = null;
     boolean mOrigWaitForDebugger = false;
     boolean mAlwaysFinishActivities = false;
+    boolean mForceResizableActivites;
     IActivityController mController = null;
     String mProfileApp = null;
     ProcessRecord mProfileProc = null;
@@ -3842,8 +3845,8 @@
             Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
             int startFlags, ProfilerInfo profilerInfo, Bundle options) {
         return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
-            resultWho, requestCode, startFlags, profilerInfo, options,
-            UserHandle.getCallingUserId());
+                resultWho, requestCode, startFlags, profilerInfo, options,
+                UserHandle.getCallingUserId());
     }
 
     @Override
@@ -4150,7 +4153,12 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        return startActivityFromRecentsInner(taskId, launchStackId, options);
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            return startActivityFromRecentsInner(taskId, launchStackId, options);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
     }
 
     final int startActivityFromRecentsInner(int taskId, int launchStackId, Bundle options) {
@@ -4172,8 +4180,12 @@
             }
 
             if (launchStackId != INVALID_STACK_ID && task.stack.mStackId != launchStackId) {
-                mStackSupervisor.moveTaskToStackUncheckedLocked(
-                        task, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
+                if (launchStackId == DOCKED_STACK_ID && options != null) {
+                    ActivityOptions activityOptions = new ActivityOptions(options);
+                    mWindowManager.setDockedStackCreateMode(activityOptions.getDockCreateMode());
+                }
+                mStackSupervisor.moveTaskToStackLocked(
+                        taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
             }
 
             if (task.getRootActivity() != null) {
@@ -9027,7 +9039,8 @@
                 }
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveActivityToStack: moving r=" + r
                         + " to stackId=" + stackId);
-                mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS);
+                mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS,
+                        "moveActivityToStack");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9047,7 +9060,8 @@
             try {
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
                         + " to stackId=" + stackId + " toTop=" + toTop);
-                mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS);
+                mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS,
+                        "moveTaskToStack");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9076,7 +9090,7 @@
                         + " to createMode=" + createMode + " toTop=" + toTop);
                 mWindowManager.setDockedStackCreateMode(createMode);
                 mStackSupervisor.moveTaskToStackLocked(
-                        taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS);
+                        taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -11638,14 +11652,17 @@
 
     private void retrieveSettings() {
         final ContentResolver resolver = mContext.getContentResolver();
-        String debugApp = Settings.Global.getString(
-            resolver, Settings.Global.DEBUG_APP);
+        String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP);
         boolean waitForDebugger = Settings.Global.getInt(
             resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0;
         boolean alwaysFinishActivities = Settings.Global.getInt(
             resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0;
         boolean forceRtl = Settings.Global.getInt(
                 resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0;
+        int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
+        boolean forceResizable = Settings.Global.getInt(
+                resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
+                defaultForceResizable) != 0;
         // Transfer any global setting for forcing RTL layout, into a System Property
         SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
 
@@ -11660,6 +11677,7 @@
             mDebugApp = mOrigDebugApp = debugApp;
             mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
             mAlwaysFinishActivities = alwaysFinishActivities;
+            mForceResizableActivites = forceResizable;
             // This happens before any activities are started, so we can
             // change mConfiguration in-place.
             updateConfigurationLocked(configuration, null, true);
@@ -12089,7 +12107,8 @@
             AppsQueryHelper queryHelper = new AppsQueryHelper(mContext);
             Set<String> enableApps = new HashSet<>();
             enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
-                            | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM,
+                            | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
+                            | AppsQueryHelper.GET_DEFAULT_IMES,
                             /* systemAppsOnly */ true, UserHandle.SYSTEM));
             ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
             enableApps.addAll(wlApps);
@@ -13065,6 +13084,13 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+        (new ActivityManagerShellCommand(this, false)).exec(
+                this, in, out, err, args, resultReceiver);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (checkCallingPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -13101,34 +13127,7 @@
                 }
                 dumpClient = true;
             } else if ("-h".equals(opt)) {
-                pw.println("Activity manager dump options:");
-                pw.println("  [-a] [-c] [-p package] [-h] [cmd] ...");
-                pw.println("  cmd may be one of:");
-                pw.println("    a[ctivities]: activity stack state");
-                pw.println("    r[recents]: recent activities state");
-                pw.println("    b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state");
-                pw.println("    i[ntents] [PACKAGE_NAME]: pending intent state");
-                pw.println("    p[rocesses] [PACKAGE_NAME]: process state");
-                pw.println("    o[om]: out of memory management");
-                pw.println("    perm[issions]: URI permission grant state");
-                pw.println("    prov[iders] [COMP_SPEC ...]: content provider state");
-                pw.println("    provider [COMP_SPEC]: provider client-side state");
-                pw.println("    s[ervices] [COMP_SPEC ...]: service state");
-                pw.println("    as[sociations]: tracked app associations");
-                pw.println("    service [COMP_SPEC]: service client-side state");
-                pw.println("    package [PACKAGE_NAME]: all state related to given package");
-                pw.println("    all: dump all activities");
-                pw.println("    top: dump the top activity");
-                pw.println("    write: write all pending state to storage");
-                pw.println("    track-associations: enable association tracking");
-                pw.println("    untrack-associations: disable and clear association tracking");
-                pw.println("  cmd may also be a COMP_SPEC to dump activities.");
-                pw.println("  COMP_SPEC may be a component name (com.foo/.myApp),");
-                pw.println("    a partial substring in a component name, a");
-                pw.println("    hex object identifier.");
-                pw.println("  -a: include all available server state.");
-                pw.println("  -c: include client state.");
-                pw.println("  -p: limit output to given package.");
+                ActivityManagerShellCommand.dumpHelp(pw, true);
                 return;
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
@@ -13265,36 +13264,15 @@
                 synchronized (this) {
                     mServices.dumpServicesLocked(fd, pw, args, opti, true, dumpClient, dumpPackage);
                 }
-            } else if ("write".equals(cmd)) {
-                mTaskPersister.flush();
-                pw.println("All tasks persisted.");
-                return;
-            } else if ("track-associations".equals(cmd)) {
-                synchronized (this) {
-                    if (!mTrackingAssociations) {
-                        mTrackingAssociations = true;
-                        pw.println("Association tracking started.");
-                    } else {
-                        pw.println("Association tracking already enabled.");
-                    }
-                }
-                return;
-            } else if ("untrack-associations".equals(cmd)) {
-                synchronized (this) {
-                    if (mTrackingAssociations) {
-                        mTrackingAssociations = false;
-                        mAssociations.clear();
-                        pw.println("Association tracking stopped.");
-                    } else {
-                        pw.println("Association tracking not running.");
-                    }
-                }
-                return;
             } else {
                 // Dumping a single activity?
                 if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) {
-                    pw.println("Bad activity command, or no activities match: " + cmd);
-                    pw.println("Use -h for help.");
+                    ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
+                    int res = shell.exec(this, null, fd, null, args, new ResultReceiver(null));
+                    if (res < 0) {
+                        pw.println("Bad activity command, or no activities match: " + cmd);
+                        pw.println("Use -h for help.");
+                    }
                 }
             }
             if (!more) {
@@ -13545,17 +13523,34 @@
         }
 
         if (mActiveUids.size() > 0) {
-            if (needSep) {
-                pw.println();
+            boolean printed = false;
+            int whichAppId = -1;
+            if (dumpPackage != null) {
+                try {
+                    ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+                            dumpPackage, 0);
+                    whichAppId = UserHandle.getAppId(info.uid);
+                } catch (NameNotFoundException e) {
+                    e.printStackTrace();
+                }
             }
-            pw.println("  UID states:");
             for (int i=0; i<mActiveUids.size(); i++) {
                 UidRecord uidRec = mActiveUids.valueAt(i);
+                if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+                    continue;
+                }
+                if (!printed) {
+                    printed = true;
+                    if (needSep) {
+                        pw.println();
+                    }
+                    pw.println("  UID states:");
+                    needSep = true;
+                    printedAnything = true;
+                }
                 pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
                 pw.print(": "); pw.println(uidRec);
             }
-            needSep = true;
-            printedAnything = true;
         }
 
         if (mLruProcesses.size() > 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
new file mode 100644
index 0000000..d1e7e85
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.IActivityManager;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+class ActivityManagerShellCommand extends ShellCommand {
+    // IPC interface to activity manager -- don't need to do additional security checks.
+    final IActivityManager mInterface;
+
+    // Internal service impl -- must perform security checks before touching.
+    final ActivityManagerService mInternal;
+
+    final boolean mDumping;
+
+    ActivityManagerShellCommand(ActivityManagerService service, boolean dumping) {
+        mInterface = service;
+        mInternal = service;
+        mDumping = dumping;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        PrintWriter pw = getOutPrintWriter();
+        try {
+            switch (cmd) {
+                case "force-stop":
+                    return runForceStop(pw);
+                case "kill":
+                    return runKill(pw);
+                case "kill-all":
+                    return runKillAll(pw);
+                case "write":
+                    return runWrite(pw);
+                case "track-associations":
+                    return runTrackAssociations(pw);
+                case "untrack-associations":
+                    return runUntrackAssociations(pw);
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    int runForceStop(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = parseUserArg(getNextArgRequired());
+            } else {
+                pw.println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        mInterface.forceStopPackage(getNextArgRequired(), userId);
+        return 0;
+    }
+
+    int runKill(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt=getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = parseUserArg(getNextArgRequired());
+            } else {
+                pw.println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        mInterface.killBackgroundProcesses(getNextArgRequired(), userId);
+        return 0;
+    }
+
+    int runKillAll(PrintWriter pw) throws RemoteException {
+        mInterface.killAllBackgroundProcesses();
+        return 0;
+    }
+
+    int runWrite(PrintWriter pw) {
+        mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+                "registerUidObserver()");
+        mInternal.mTaskPersister.flush();
+        pw.println("All tasks persisted.");
+        return 0;
+    }
+
+    int runTrackAssociations(PrintWriter pw) {
+        mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+                "registerUidObserver()");
+        synchronized (mInternal) {
+            if (!mInternal.mTrackingAssociations) {
+                mInternal.mTrackingAssociations = true;
+                pw.println("Association tracking started.");
+            } else {
+                pw.println("Association tracking already enabled.");
+            }
+        }
+        return 0;
+    }
+
+    int runUntrackAssociations(PrintWriter pw) {
+        mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+                "registerUidObserver()");
+        synchronized (mInternal) {
+            if (mInternal.mTrackingAssociations) {
+                mInternal.mTrackingAssociations = false;
+                mInternal.mAssociations.clear();
+                pw.println("Association tracking stopped.");
+            } else {
+                pw.println("Association tracking not running.");
+            }
+        }
+        return 0;
+    }
+
+    int parseUserArg(String arg) {
+        int userId;
+        if ("all".equals(arg)) {
+            userId = UserHandle.USER_ALL;
+        } else if ("current".equals(arg) || "cur".equals(arg)) {
+            userId = UserHandle.USER_CURRENT;
+        } else {
+            try {
+                userId = Integer.parseInt(arg);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Bad user number: " + arg);
+            }
+        }
+        return userId;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        dumpHelp(pw, mDumping);
+    }
+
+    static void dumpHelp(PrintWriter pw, boolean dumping) {
+        if (dumping) {
+            pw.println("Activity manager dump options:");
+            pw.println("  [-a] [-c] [-p PACKAGE] [-h] [WHAT] ...");
+            pw.println("  WHAT may be one of:");
+            pw.println("    a[ctivities]: activity stack state");
+            pw.println("    r[recents]: recent activities state");
+            pw.println("    b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state");
+            pw.println("    i[ntents] [PACKAGE_NAME]: pending intent state");
+            pw.println("    p[rocesses] [PACKAGE_NAME]: process state");
+            pw.println("    o[om]: out of memory management");
+            pw.println("    perm[issions]: URI permission grant state");
+            pw.println("    prov[iders] [COMP_SPEC ...]: content provider state");
+            pw.println("    provider [COMP_SPEC]: provider client-side state");
+            pw.println("    s[ervices] [COMP_SPEC ...]: service state");
+            pw.println("    as[sociations]: tracked app associations");
+            pw.println("    service [COMP_SPEC]: service client-side state");
+            pw.println("    package [PACKAGE_NAME]: all state related to given package");
+            pw.println("    all: dump all activities");
+            pw.println("    top: dump the top activity");
+            pw.println("  WHAT may also be a COMP_SPEC to dump activities.");
+            pw.println("  COMP_SPEC may be a component name (com.foo/.myApp),");
+            pw.println("    a partial substring in a component name, a");
+            pw.println("    hex object identifier.");
+            pw.println("  -a: include all available server state.");
+            pw.println("  -c: include client state.");
+            pw.println("  -p: limit output to given package.");
+        } else {
+            pw.println("Activity manager (activity) commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println("  force-stop [--user <USER_ID> | all | current] <PACKAGE>");
+            pw.println("    Complete stop the given application package.");
+            pw.println("  kill [--user <USER_ID> | all | current] <PACKAGE>");
+            pw.println("    Kill all processes associated with the given application.");
+            pw.println("  kill-all");
+            pw.println("    Kill all processes that are safe to kill (cached, etc)");
+            pw.println("  write");
+            pw.println("    Write all pending state to storage.");
+            pw.println("  track-associations");
+            pw.println("    Enable association tracking.");
+            pw.println("  untrack-associations");
+            pw.println("    Disable and clear association tracking.");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 4671cb0..8061a92 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -53,6 +53,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.view.AppTransitionAnimationSpec;
 import android.view.IApplicationToken;
 import android.view.WindowManager;
 
@@ -863,17 +864,24 @@
                     break;
                 case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP:
                 case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
-                    service.mWindowManager.overridePendingAppTransitionAspectScaledThumb(
-                            pendingOptions.getThumbnail(),
-                            pendingOptions.getStartX(), pendingOptions.getStartY(),
-                            pendingOptions.getWidth(), pendingOptions.getHeight(),
-                            pendingOptions.getOnAnimationStartListener(),
-                            (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP));
-                    if (intent.getSourceBounds() == null) {
-                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
-                                pendingOptions.getStartY(),
-                                pendingOptions.getStartX() + pendingOptions.getWidth(),
-                                pendingOptions.getStartY() + pendingOptions.getHeight()));
+                    final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
+                    if (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
+                            && specs != null) {
+                        service.mWindowManager.overridePendingAppTransitionMultiThumb(
+                                specs, pendingOptions.getOnAnimationStartListener(), false);
+                    } else {
+                        service.mWindowManager.overridePendingAppTransitionAspectScaledThumb(
+                                pendingOptions.getThumbnail(),
+                                pendingOptions.getStartX(), pendingOptions.getStartY(),
+                                pendingOptions.getWidth(), pendingOptions.getHeight(),
+                                pendingOptions.getOnAnimationStartListener(),
+                                (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP));
+                        if (intent.getSourceBounds() == null) {
+                            intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+                                    pendingOptions.getStartY(),
+                                    pendingOptions.getStartX() + pendingOptions.getWidth(),
+                                    pendingOptions.getStartY() + pendingOptions.getHeight()));
+                        }
                     }
                     break;
                 default:
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 99539e4..2104830 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
+import static android.app.ActivityManager.INVALID_STACK_ID;
 import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
 import static android.app.ActivityManager.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
@@ -529,25 +530,35 @@
      * @param task If non-null, the task will be moved to the top of the stack.
      * */
     void moveToFront(String reason, TaskRecord task) {
-        if (isAttached()) {
-            final ActivityStack lastFocusStack = mStacks.get(mStacks.size() - 1);
-            // Need to move this stack to the front before calling
-            // {@link ActivityStackSupervisor#setFocusStack} below.
-            mStacks.remove(this);
-            mStacks.add(this);
+        if (!isAttached()) {
+            return;
+        }
 
-            // TODO(multi-display): Needs to also work if focus is moving to the non-home display.
-            if (isOnHomeDisplay()) {
-                mStackSupervisor.setFocusStack(reason, lastFocusStack);
+        mStacks.remove(this);
+        int addIndex = mStacks.size();
+
+        if (addIndex > 0) {
+            final ActivityStack topStack = mStacks.get(addIndex - 1);
+            if (topStack.mStackId == PINNED_STACK_ID && topStack != this) {
+                // The pinned stack is always the top most stack (always-on-top).
+                // So, stack is moved just below the pinned stack.
+                addIndex--;
             }
-            if (task != null) {
-                insertTaskAtTop(task, null);
-            } else {
-                task = topTask();
-            }
-            if (task != null) {
-                mWindowManager.moveTaskToTop(task.taskId);
-            }
+        }
+
+        mStacks.add(addIndex, this);
+
+        // TODO(multi-display): Needs to also work if focus is moving to the non-home display.
+        if (isOnHomeDisplay()) {
+            mStackSupervisor.setFocusStack(reason, this);
+        }
+        if (task != null) {
+            insertTaskAtTop(task, null);
+        } else {
+            task = topTask();
+        }
+        if (task != null) {
+            mWindowManager.moveTaskToTop(task.taskId);
         }
     }
 
@@ -1303,12 +1314,7 @@
             return false;
         }
 
-        if (mStackSupervisor.isFrontStack(this)) {
-            return true;
-        }
-
-        if (mStackId == PINNED_STACK_ID) {
-            // Pinned stack is always visible if it exist.
+        if (mStackSupervisor.isFrontStack(this) || mStackSupervisor.isFocusedStack(this)) {
             return true;
         }
 
@@ -2040,7 +2046,7 @@
             // Have the window manager re-evaluate the orientation of
             // the screen based on the new activity order.
             boolean notUpdated = true;
-            if (mStackSupervisor.isFrontStack(this)) {
+            if (mStackSupervisor.isFocusedStack(this)) {
                 Configuration config = mWindowManager.updateOrientationFromAppTokens(
                         mService.mConfiguration,
                         next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
@@ -2787,7 +2793,7 @@
     }
 
     private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
-        if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
+        if (mStackSupervisor.isFocusedStack(this) && mService.mFocusedActivity == r) {
             ActivityRecord next = topRunningActivityLocked();
             final String myReason = reason + " adjustFocus";
             if (next != r) {
@@ -3394,7 +3400,7 @@
         if (task != null && task.removeActivity(r)) {
             if (DEBUG_STACK) Slog.i(TAG_STACK,
                     "removeActivityFromHistoryLocked: last activity removed from " + this);
-            if (mStackSupervisor.isFrontStack(this) && task == topTask() &&
+            if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
                     task.isOverHomeStack()) {
                 mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), reason);
             }
@@ -4375,6 +4381,7 @@
 
             RunningTaskInfo ci = new RunningTaskInfo();
             ci.id = task.taskId;
+            ci.stackId = mStackId;
             ci.baseActivity = r.intent.getComponent();
             ci.topActivity = top.intent.getComponent();
             ci.lastActiveTime = task.lastActiveTime;
@@ -4567,7 +4574,7 @@
         if (mTaskHistory.isEmpty()) {
             if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
             // We only need to adjust focused stack if this stack is in focus.
-            if (isOnHomeDisplay() && mStackSupervisor.isFrontStack(this)) {
+            if (isOnHomeDisplay() && mStackSupervisor.isFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
                 if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) {
                     mStackSupervisor.moveHomeStackToFront(myReason);
@@ -4675,7 +4682,7 @@
             return;
         }
 
-        final boolean wasFocused = mStackSupervisor.isFrontStack(prevStack)
+        final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack)
                 && (mStackSupervisor.topRunningActivityLocked() == r);
         final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 98cfc7c..1cd71a1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -458,10 +458,7 @@
         return mLastFocusedStack;
     }
 
-    /** Top of all visible stacks is/should always be equal to the focused stack.
-     * Use {@link ActivityStack#isStackVisibleLocked} to determine if a specific
-     * stack is visible or not. */
-    boolean isFrontStack(ActivityStack stack) {
+    boolean isFocusedStack(ActivityStack stack) {
         if (stack == null) {
             return false;
         }
@@ -473,18 +470,22 @@
         return stack == mFocusedStack;
     }
 
-    void setFocusStack(String reason, ActivityStack lastFocusedStack) {
-        ArrayList<ActivityStack> stacks = mHomeStack.mStacks;
-        final int topNdx = stacks.size() - 1;
-        if (topNdx <= 0) {
-            return;
+    /** The top most stack. */
+    boolean isFrontStack(ActivityStack stack) {
+        if (stack == null) {
+            return false;
         }
 
-        final ActivityStack topStack = stacks.get(topNdx);
-        mFocusedStack = topStack;
-        if (lastFocusedStack != null) {
-            mLastFocusedStack = lastFocusedStack;
+        final ActivityRecord parent = stack.mActivityContainer.mParentActivity;
+        if (parent != null) {
+            stack = parent.task.stack;
         }
+        return stack == mHomeStack.mStacks.get((mHomeStack.mStacks.size() - 1));
+    }
+
+    void setFocusStack(String reason, ActivityStack focusedStack) {
+        mLastFocusedStack = mFocusedStack;
+        mFocusedStack = focusedStack;
 
         EventLogTags.writeAmFocusedStack(
                 mCurrentUser, mFocusedStack == null ? -1 : mFocusedStack.getStackId(),
@@ -642,7 +643,7 @@
             ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
-                if (!isFrontStack(stack)) {
+                if (!isFocusedStack(stack)) {
                     continue;
                 }
                 ActivityRecord hr = stack.topRunningActivityLocked();
@@ -673,7 +674,7 @@
             ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
-                if (!isFrontStack(stack) || stack.numActivities() == 0) {
+                if (!isFocusedStack(stack) || stack.numActivities() == 0) {
                     continue;
                 }
                 final ActivityRecord resumedActivity = stack.mResumedActivity;
@@ -692,7 +693,7 @@
             ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
-                if (isFrontStack(stack)) {
+                if (isFocusedStack(stack)) {
                     final ActivityRecord r = stack.mResumedActivity;
                     if (r != null && r.state != RESUMED) {
                         return false;
@@ -737,7 +738,7 @@
             ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
-                if (!isFrontStack(stack) && stack.mResumedActivity != null) {
+                if (!isFocusedStack(stack) && stack.mResumedActivity != null) {
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
                             " mResumedActivity=" + stack.mResumedActivity);
                     someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
@@ -1374,7 +1375,7 @@
         // launching the initial activity (that is, home), so that it can have
         // a chance to initialize itself while in the background, making the
         // switch back to it faster and look better.
-        if (isFrontStack(stack)) {
+        if (isFocusedStack(stack)) {
             mService.startSetupActivityLocked();
         }
 
@@ -2621,7 +2622,7 @@
             r.idle = true;
 
             //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
-            if (isFrontStack(r.task.stack) || fromTimeout) {
+            if (isFocusedStack(r.task.stack) || fromTimeout) {
                 booting = checkFinishBootingLocked();
             }
         }
@@ -2749,8 +2750,7 @@
         boolean didSomething = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int numStacks = stacks.size();
-            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
                 if (stack.finishDisabledPackageActivitiesLocked(
                         packageName, filterByClasses, doit, evenPersistent, userId)) {
@@ -2774,7 +2774,7 @@
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
-                if (isFrontStack(stack)) {
+                if (isFocusedStack(stack)) {
                     if (stack.mResumedActivity != null) {
                         fgApp = stack.mResumedActivity.app;
                     } else if (stack.mPausingActivity != null) {
@@ -2806,7 +2806,7 @@
         }
         // Do targetStack first.
         boolean result = false;
-        if (isFrontStack(targetStack)) {
+        if (isFocusedStack(targetStack)) {
             result = targetStack.resumeTopActivityLocked(target, targetOptions);
         }
 
@@ -2818,7 +2818,7 @@
                     // Already started above.
                     continue;
                 }
-                if (isFrontStack(stack)) {
+                if (isFocusedStack(stack)) {
                     stack.resumeTopActivityLocked(null);
                 }
             }
@@ -2876,13 +2876,16 @@
         }
 
         if (stackId != task.stack.mStackId) {
-            moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, FORCE_FOCUS, reason);
-        } else {
-            task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
-                task.getTopActivity() == null ? null : task.getTopActivity().appTimeTracker,
-                reason);
+            moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+
+            // moveTaskToStackUncheckedLocked() should already placed the task on top,
+            // still need moveTaskToFrontLocked() below for any transition settings.
         }
 
+        final ActivityRecord r = task.getTopActivity();
+        task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
+                r == null ? null : r.appTimeTracker, reason);
+
         if (DEBUG_STACK) Slog.d(TAG_STACK,
                 "findTaskToMoveToFront: moved to front of stack=" + task.stack);
     }
@@ -2989,84 +2992,87 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
+        mWindowManager.deferSurfaceLayout();
+        try {
+            ActivityRecord r = stack.topRunningActivityLocked();
 
-        ActivityRecord r = stack.topRunningActivityLocked();
-
-        mTmpBounds.clear();
-        mTmpConfigs.clear();
-        ArrayList<TaskRecord> tasks = stack.getAllTasks();
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            TaskRecord task = tasks.get(i);
-            if (task.mResizeable) {
-                if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
-                    // For freeform stack we don't adjust the size of the tasks to match that
-                    // of the stack, but we do try to make sure the tasks are still contained
-                    // with the bounds of the stack.
-                    tempRect2.set(task.mBounds);
-                    fitWithinBounds(tempRect2, bounds);
-                    task.updateOverrideConfiguration(tempRect2);
-                } else {
-                    task.updateOverrideConfiguration(bounds);
-                }
-            }
-
-            mTmpConfigs.put(task.taskId, task.mOverrideConfig);
-            mTmpBounds.put(task.taskId, task.mBounds);
-        }
-        stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds);
-        if (stack.mStackId == DOCKED_STACK_ID) {
-            // Dock stack funness...Yay!
-            if (stack.mFullscreen) {
-                // The dock stack went fullscreen which is kinda like dismissing it.
-                // In this case we make all other static stacks fullscreen and move all
-                // docked stack tasks to the fullscreen stack.
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (i != DOCKED_STACK_ID && getStack(i) != null) {
-                        resizeStackLocked(i, null, preserveWindows, true);
+            mTmpBounds.clear();
+            mTmpConfigs.clear();
+            ArrayList<TaskRecord> tasks = stack.getAllTasks();
+            for (int i = tasks.size() - 1; i >= 0; i--) {
+                TaskRecord task = tasks.get(i);
+                if (task.mResizeable) {
+                    if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                        // For freeform stack we don't adjust the size of the tasks to match that
+                        // of the stack, but we do try to make sure the tasks are still contained
+                        // with the bounds of the stack.
+                        tempRect2.set(task.mBounds);
+                        fitWithinBounds(tempRect2, bounds);
+                        task.updateOverrideConfiguration(tempRect2);
+                    } else {
+                        task.updateOverrideConfiguration(bounds);
                     }
                 }
 
-                final int count = tasks.size();
-                for (int i = 0; i < count; i++) {
-                    moveTaskToStackLocked(tasks.get(i).taskId,
-                            FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS);
-                }
+                mTmpConfigs.put(task.taskId, task.mOverrideConfig);
+                mTmpBounds.put(task.taskId, task.mBounds);
+            }
+            stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds);
+            if (stack.mStackId == DOCKED_STACK_ID) {
+                // Dock stack funness...Yay!
+                if (stack.mFullscreen) {
+                    // The dock stack went fullscreen which is kinda like dismissing it.
+                    // In this case we make all other static stacks fullscreen and move all
+                    // docked stack tasks to the fullscreen stack.
+                    for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+                        if (i != DOCKED_STACK_ID && getStack(i) != null) {
+                            resizeStackLocked(i, null, preserveWindows, true);
+                        }
+                    }
 
-                // stack shouldn't contain anymore activities, so nothing to resume.
-                r = null;
-            } else {
-                // Docked stacks occupy a dedicated region on screen so the size of all other
-                // static stacks need to be adjusted so they don't overlap with the docked stack.
-                // We get the bounds to use from window manager which has been adjusted for any
-                // screen controls and is also the same for all stacks.
-                mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
+                    final int count = tasks.size();
+                    for (int i = 0; i < count; i++) {
+                        moveTaskToStackLocked(tasks.get(i).taskId,
+                                FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack");
+                    }
 
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (i != DOCKED_STACK_ID) {
-                        ActivityStack otherStack = getStack(i);
-                        if (otherStack != null) {
-                            resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
+                    // stack shouldn't contain anymore activities, so nothing to resume.
+                    r = null;
+                } else {
+                    // Docked stacks occupy a dedicated region on screen so the size of all other
+                    // static stacks need to be adjusted so they don't overlap with the docked stack.
+                    // We get the bounds to use from window manager which has been adjusted for any
+                    // screen controls and is also the same for all stacks.
+                    mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
+
+                    for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+                        if (i != DOCKED_STACK_ID) {
+                            ActivityStack otherStack = getStack(i);
+                            if (otherStack != null) {
+                                resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
+                            }
                         }
                     }
                 }
+                // Since we are resizing the stack, all other operations should strive to preserve
+                // windows.
+                preserveWindows = true;
             }
-            // Since we are resizing the stack, all other operations should strive to preserve
-            // windows.
-            preserveWindows = true;
-        }
-        stack.setBounds(bounds);
+            stack.setBounds(bounds);
 
-        if (r != null) {
-            final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows);
-            // And we need to make sure at this point that all other activities
-            // are made visible with the correct configuration.
-            ensureActivitiesVisibleLocked(r, 0, preserveWindows);
-            if (!updated) {
-                resumeTopActivitiesLocked(stack, null, null);
+            if (r != null) {
+                final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows);
+                // And we need to make sure at this point that all other activities
+                // are made visible with the correct configuration.
+                ensureActivitiesVisibleLocked(r, 0, preserveWindows);
+                if (!updated) {
+                    resumeTopActivitiesLocked(stack, null, null);
+                }
             }
+        } finally {
+            mWindowManager.continueSurfaceLayout();
+            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
-
-        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) {
@@ -3238,7 +3244,7 @@
     ActivityStack moveTaskToStackUncheckedLocked(
             TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
         final ActivityRecord r = task.getTopActivity();
-        final boolean wasFocused = isFrontStack(task.stack) && (topRunningActivityLocked() == r);
+        final boolean wasFocused = isFocusedStack(task.stack) && (topRunningActivityLocked() == r);
         final boolean wasResumed = wasFocused && (task.stack.mResumedActivity == r);
 
         final boolean resizeable = task.mResizeable;
@@ -3262,13 +3268,13 @@
         return stack;
     }
 
-    void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus) {
+    void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
+            String reason) {
         final TaskRecord task = anyTaskForIdLocked(taskId);
         if (task == null) {
             Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
             return;
         }
-        final String reason = "moveTaskToStack";
         if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID
                 || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
             // We are about to relaunch the activity because its configuration changed due to
@@ -3281,7 +3287,8 @@
             mWindowManager.setReplacingWindow(r.appToken, true /* animate */);
         }
         final ActivityStack stack =
-                moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus, reason);
+                moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus,
+                        "moveTaskToStack:" + reason);
 
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
@@ -3327,7 +3334,8 @@
         if (task.mActivities.size() == 1) {
             // There is only one activity in the task. So, we can just move the task over to the
             // pinned stack without re-parenting the activity in a different task.
-            moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS);
+            moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS,
+                    "moveTopActivityToPinnedStack");
         } else {
             final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
             pinnedStack.moveActivityToStack(r);
@@ -3462,7 +3470,7 @@
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
                 stack.awakeFromSleepingLocked();
-                if (isFrontStack(stack)) {
+                if (isFocusedStack(stack)) {
                     resumeTopActivitiesLocked();
                 }
             }
@@ -3529,7 +3537,7 @@
 
     boolean reportResumedActivityLocked(ActivityRecord r) {
         final ActivityStack stack = r.task.stack;
-        if (isFrontStack(stack)) {
+        if (isFocusedStack(stack)) {
             mService.updateUsageStats(r, true);
         }
         if (allResumedActivitiesComplete()) {
@@ -3822,7 +3830,7 @@
                 final ActivityStack stack = stacks.get(stackNdx);
                 final ActivityRecord r = stack.topRunningActivityLocked();
                 final ActivityState state = r == null ? DESTROYED : r.state;
-                if (isFrontStack(stack)) {
+                if (isFocusedStack(stack)) {
                     if (r == null) Slog.e(TAG,
                             "validateTop...: null top activity, stack=" + stack);
                     else {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 090a342..120b40c 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -318,7 +318,7 @@
         mNextAffiliateTaskId = nextTaskId;
         mCallingUid = callingUid;
         mCallingPackage = callingPackage;
-        mResizeable = resizeable;
+        mResizeable = resizeable || mService.mForceResizableActivites;
         mPrivileged = privileged;
         ActivityInfo info = mActivities.get(0).info;
         mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
@@ -420,7 +420,7 @@
         } else {
             autoRemoveRecents = false;
         }
-        mResizeable = info.resizeable;
+        mResizeable = info.resizeable || mService.mForceResizableActivites;
         mLockTaskMode = info.lockTaskLaunchMode;
         mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
         setLockTaskAuth();
@@ -626,6 +626,9 @@
         // Only set this based on the first activity
         if (mActivities.isEmpty()) {
             taskType = r.mActivityType;
+            if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) {
+                mResizeable = r.info.resizeable;
+            }
             isPersistable = r.isPersistable();
             mCallingUid = r.launchedFromUid;
             mCallingPackage = r.launchedFromPackage;
diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/services/core/java/com/android/server/input/InputWindowHandle.java
index 9149fcc..207c05d 100644
--- a/services/core/java/com/android/server/input/InputWindowHandle.java
+++ b/services/core/java/com/android/server/input/InputWindowHandle.java
@@ -100,6 +100,17 @@
     }
 
     @Override
+    public String toString() {
+        return new StringBuilder(name)
+                .append(", layer=").append(layer)
+                .append(", frame=[").append(frameLeft).append(",").append(frameTop).append(",")
+                        .append(frameRight).append(",").append(frameBottom).append("]")
+                .append(", touchableRegion=").append(touchableRegion)
+                .toString();
+
+    }
+
+    @Override
     protected void finalize() throws Throwable {
         try {
             nativeDispose();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e8e46ef..361bbf9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2146,13 +2146,6 @@
                     + " id=" + id + " notification=" + notification);
         }
 
-        if (notification.getSmallIcon() != null) {
-            if (!notification.isValid()) {
-                throw new IllegalArgumentException("Invalid notification (): pkg=" + pkg
-                        + " id=" + id + " notification=" + notification);
-            }
-        }
-
         mHandler.post(new Runnable() {
             @Override
             public void run() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b3e7f5f..be9f44d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1753,11 +1753,21 @@
 
         PermissionsState permissionsState = sb.getPermissionsState();
 
-        for (String permission : pkg.requestedPermissions) {
-            BasePermission bp = mSettings.mPermissions.get(permission);
-            if (bp != null && bp.isRuntime() && (grantedPermissions == null
-                    || ArrayUtils.contains(grantedPermissions, permission))) {
-                permissionsState.grantRuntimePermission(bp, userId);
+        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+
+        synchronized (mPackages) {
+            for (String permission : pkg.requestedPermissions) {
+                BasePermission bp = mSettings.mPermissions.get(permission);
+                if (bp != null && (bp.isRuntime() || bp.isDevelopment())
+                        && (grantedPermissions == null
+                               || ArrayUtils.contains(grantedPermissions, permission))) {
+                    final int flags = permissionsState.getPermissionFlags(permission, userId);
+                    // Installer cannot change immutable permissions.
+                    if ((flags & immutableFlags) == 0) {
+                        grantRuntimePermission(pkg.packageName, permission, userId);
+                    }
+                }
             }
         }
     }
@@ -2276,7 +2286,7 @@
                         mSettings.enableSystemPackageLPw(packageName);
 
                         try {
-                            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, UserHandle.SYSTEM);
+                            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);
                         } catch (PackageManagerException e) {
                             Slog.e(TAG, "Failed to parse original system package: "
                                     + e.getMessage());
@@ -3558,7 +3568,8 @@
                             killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
                         }
                     });
-                } break;
+                }
+                break;
             }
 
             mOnPermissionChangeListeners.onPermissionsChanged(uid);
@@ -5685,7 +5696,7 @@
             }
             try {
                 scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
-                        scanFlags, currentTime, UserHandle.SYSTEM);
+                        scanFlags, currentTime, null);
             } catch (PackageManagerException e) {
                 Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
 
@@ -5791,8 +5802,6 @@
      */
     private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
             long currentTime, UserHandle user) throws PackageManagerException {
-        Preconditions.checkNotNull(user);
-
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
         parseFlags |= mDefParseFlags;
         PackageParser pp = new PackageParser();
@@ -12141,7 +12150,7 @@
                 int oldScanFlags = SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME;
                 try {
                     scanPackageTracedLI(restoreFile, oldParseFlags, oldScanFlags, origUpdateTime,
-                            UserHandle.SYSTEM);
+                            null);
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade: "
                             + e.getMessage());
@@ -13276,8 +13285,7 @@
 
         final PackageParser.Package newPkg;
         try {
-            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0,
-                    UserHandle.SYSTEM);
+            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, null);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + newPs.name + ": " + e.getMessage());
             return false;
@@ -15841,8 +15849,7 @@
                 synchronized (mInstallLock) {
                     PackageParser.Package pkg = null;
                     try {
-                        pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0,
-                                UserHandle.SYSTEM);
+                        pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0, null);
                     } catch (PackageManagerException e) {
                         Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
                     }
@@ -16005,8 +16012,7 @@
             synchronized (mInstallLock) {
                 final PackageParser.Package pkg;
                 try {
-                    pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0,
-                            UserHandle.SYSTEM);
+                    pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, null);
                     loaded.add(pkg.applicationInfo);
                 } catch (PackageManagerException e) {
                     Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage());
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b36a22e..4dd7388 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,14 +16,15 @@
 
 package com.android.server.pm;
 
-import android.accounts.Account;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
 import android.app.IStopUserCallback;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -46,6 +47,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.system.ErrnoException;
@@ -59,9 +61,11 @@
 import android.util.TimeUtils;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.LocalServices;
 
@@ -83,11 +87,18 @@
 
 import libcore.io.IoUtils;
 
+/**
+ * Service for {@link UserManager}.
+ *
+ * Method naming convention:
+ * - Methods suffixed with "Locked" should be called within the {@code this} lock.
+ * - Methods suffixed with "RL" should be called within the {@link #mRestrictionsLock} lock.
+ */
 public class UserManagerService extends IUserManager.Stub {
 
     private static final String LOG_TAG = "UserManagerService";
 
-    private static final boolean DBG = false;
+    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG_NAME = "name";
     private static final String ATTR_FLAGS = "flags";
@@ -160,7 +171,38 @@
     private final File mUserListFile;
 
     private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
-    private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
+
+    private final Object mRestrictionsLock = new Object();
+
+    /**
+     * User restrictions set via UserManager.  This doesn't include restrictions set by
+     * device owner / profile owners.
+     *
+     * DO NOT Change existing {@link Bundle} in it.  When changing a restriction for a user,
+     * a new {@link Bundle} should always be created and set.  This is because a {@link Bundle}
+     * maybe shared between {@link #mBaseUserRestrictions} and
+     * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
+     * (Otherwise we won't be able to detect what restrictions have changed in
+     * {@link #updateUserRestrictionsInternalRL).
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>();
+
+    /**
+     * Cached user restrictions that are in effect -- i.e. {@link #mBaseUserRestrictions} combined
+     * with device / profile owner restrictions.  We'll initialize it lazily; use
+     * {@link #getEffectiveUserRestrictions} to access it.
+     *
+     * DO NOT Change existing {@link Bundle} in it.  When changing a restriction for a user,
+     * a new {@link Bundle} should always be created and set.  This is because a {@link Bundle}
+     * maybe shared between {@link #mBaseUserRestrictions} and
+     * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
+     * (Otherwise we won't be able to detect what restrictions have changed in
+     * {@link #updateUserRestrictionsInternalRL).
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
+
     private final Bundle mGuestRestrictions = new Bundle();
 
     /**
@@ -176,6 +218,8 @@
 
     private IAppOpsService mAppOpsService;
 
+    private final LocalService mLocalService;
+
     private static UserManagerService sInstance;
 
     public static UserManagerService getInstance() {
@@ -231,6 +275,8 @@
                 sInstance = this;
             }
         }
+        mLocalService = new LocalService();
+        LocalServices.addService(UserManagerInternal.class, mLocalService);
     }
 
     void systemReady() {
@@ -258,8 +304,9 @@
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
         for (int i = 0; i < mUserIds.length; ++i) {
+            final int userId = mUserIds[i];
             try {
-                mAppOpsService.setUserRestrictions(mUserRestrictions.get(mUserIds[i]), mUserIds[i]);
+                mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
             }
@@ -588,75 +635,171 @@
         }
     }
 
-    @Override
-    public boolean hasUserRestriction(String restrictionKey, int userId) {
-        synchronized (mPackagesLock) {
-            Bundle restrictions = mUserRestrictions.get(userId);
-            return restrictions != null && restrictions.getBoolean(restrictionKey);
+    @GuardedBy("mRestrictionsLock")
+    private Bundle computeEffectiveUserRestrictionsRL(int userId) {
+        final DevicePolicyManagerInternal dpmi =
+                LocalServices.getService(DevicePolicyManagerInternal.class);
+        final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
+
+        final Bundle effective;
+        if (dpmi == null) {
+            // TODO Make sure it's because DPMS is disabled and not because we called it too early.
+            effective = systemRestrictions;
+        } else {
+            effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions);
+        }
+        return effective;
+    }
+
+    @GuardedBy("mRestrictionsLock")
+    private void invalidateEffectiveUserRestrictionsRL(int userId) {
+        if (DBG) {
+            Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
+        }
+        mCachedEffectiveUserRestrictions.remove(userId);
+    }
+
+    private Bundle getEffectiveUserRestrictions(int userId) {
+        synchronized (mRestrictionsLock) {
+            Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
+            if (restrictions == null) {
+                restrictions = computeEffectiveUserRestrictionsRL(userId);
+                mCachedEffectiveUserRestrictions.put(userId, restrictions);
+            }
+            return restrictions;
         }
     }
 
+    /** @return a specific user restriction that's in effect currently. */
+    @Override
+    public boolean hasUserRestriction(String restrictionKey, int userId) {
+        Bundle restrictions = getEffectiveUserRestrictions(userId);
+        return restrictions != null && restrictions.getBoolean(restrictionKey);
+    }
+
+    /**
+     * @return UserRestrictions that are in effect currently.  This always returns a new
+     * {@link Bundle}.
+     */
     @Override
     public Bundle getUserRestrictions(int userId) {
-        synchronized (mPackagesLock) {
-            Bundle restrictions = mUserRestrictions.get(userId);
-            return restrictions != null ? new Bundle(restrictions) : new Bundle();
-        }
+        Bundle restrictions = getEffectiveUserRestrictions(userId);
+        return restrictions != null ? new Bundle(restrictions) : new Bundle();
     }
 
     @Override
     public void setUserRestriction(String key, boolean value, int userId) {
         checkManageUsersPermission("setUserRestriction");
-        synchronized (mPackagesLock) {
-            if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
-                Bundle restrictions = getUserRestrictions(userId);
-                restrictions.putBoolean(key, value);
-                setUserRestrictionsInternalLocked(restrictions, userId);
-            }
+        if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
+            setUserRestrictionNoCheck(key, value, userId);
         }
     }
 
     @Override
     public void setSystemControlledUserRestriction(String key, boolean value, int userId) {
         checkSystemOrRoot("setSystemControlledUserRestriction");
-        synchronized (mPackagesLock) {
-            Bundle restrictions = getUserRestrictions(userId);
-            restrictions.putBoolean(key, value);
-            setUserRestrictionsInternalLocked(restrictions, userId);
+        setUserRestrictionNoCheck(key, value, userId);
+    }
+
+    private void setUserRestrictionNoCheck(String key, boolean value, int userId) {
+        synchronized (mRestrictionsLock) {
+            // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
+            // a copy.
+            final Bundle newRestrictions = new Bundle();
+            UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
+            newRestrictions.putBoolean(key, value);
+
+            updateUserRestrictionsInternalRL(newRestrictions, userId);
         }
     }
 
-    @Override
-    public void setUserRestrictions(Bundle restrictions, int userId) {
-        checkManageUsersPermission("setUserRestrictions");
-        if (restrictions == null) return;
-
-        synchronized (mPackagesLock) {
-            final Bundle oldUserRestrictions = mUserRestrictions.get(userId);
-            // Restore the original state of system controlled restrictions from oldUserRestrictions
-            for (String key : UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS) {
-                restrictions.remove(key);
-                if (oldUserRestrictions.containsKey(key)) {
-                    restrictions.putBoolean(key, oldUserRestrictions.getBoolean(key));
-                }
-            }
-            setUserRestrictionsInternalLocked(restrictions, userId);
+    /**
+     * Optionally updating user restrictions, calculate the effective user restrictions by
+     * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also
+     * apply it to {@link com.android.server.AppOpsService}.
+     * TODO applyUserRestrictionsLocked() should also apply to system settings.
+     *
+     * @param newRestrictions User restrictions to set.  If null, only the effective restrictions
+     *     will be updated.  Note don't pass an existing Bundle in {@link #mBaseUserRestrictions}
+     *     or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub
+     * @param userId target user ID.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private void updateUserRestrictionsInternalRL(
+            @Nullable Bundle newRestrictions, int userId) {
+        if (DBG) {
+            Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
+                    + " bundle=" + newRestrictions);
         }
+        final Bundle prevRestrictions = getEffectiveUserRestrictions(userId);
+
+        // Update system restrictions.
+        if (newRestrictions != null) {
+            // If newRestrictions == the current one, it's probably a bug.
+            Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions);
+            Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
+                    != newRestrictions);
+            mBaseUserRestrictions.put(userId, newRestrictions);
+        }
+
+        mCachedEffectiveUserRestrictions.put(
+                userId, computeEffectiveUserRestrictionsRL(userId));
+
+        applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions);
     }
 
-    private void setUserRestrictionsInternalLocked(Bundle restrictions, int userId) {
-        final Bundle userRestrictions = mUserRestrictions.get(userId);
-        userRestrictions.clear();
-        userRestrictions.putAll(restrictions);
-        long token = Binder.clearCallingIdentity();
+    @GuardedBy("mRestrictionsLock")
+    private void applyUserRestrictionsRL(int userId,
+            Bundle newRestrictions, Bundle prevRestrictions) {
+        final long token = Binder.clearCallingIdentity();
         try {
-        mAppOpsService.setUserRestrictions(userRestrictions, userId);
+            mAppOpsService.setUserRestrictions(newRestrictions, userId);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        scheduleWriteUserLocked(mUsers.get(userId));
+
+        // TODO Move the code from DPMS.setUserRestriction().
+    }
+
+    @GuardedBy("mRestrictionsLock")
+    private void updateEffectiveUserRestrictionsRL(int userId) {
+        updateUserRestrictionsInternalRL(null, userId);
+    }
+
+    @GuardedBy("mRestrictionsLock")
+    private void updateEffectiveUserRestrictionsForAllUsersRL() {
+        // First, invalidate all cached values.
+        synchronized (mRestrictionsLock) {
+            mCachedEffectiveUserRestrictions.clear();
+        }
+        // We don't want to call into ActivityManagerNative while taking a lock, so we'll call
+        // it on a handler.
+        final Runnable r = new Runnable() {
+            @Override
+            public void run() {
+                // Then get the list of running users.
+                final int[] runningUsers;
+                try {
+                    runningUsers = ActivityManagerNative.getDefault().getRunningUserIds();
+                } catch (RemoteException e) {
+                    Log.w(LOG_TAG, "Unable to access ActivityManagerNative");
+                    return;
+                }
+                // Then re-calculate the effective restrictions and apply, only for running users.
+                // It's okay if a new user has started after the getRunningUserIds() call,
+                // because we'll do the same thing (re-calculate the restrictions and apply)
+                // when we start a user.
+                // TODO: "Apply restrictions upon user start hasn't been implemented.  Implement it.
+                synchronized (mRestrictionsLock) {
+                    for (int i = 0; i < runningUsers.length; i++) {
+                        updateUserRestrictionsInternalRL(null, runningUsers[i]);
+                    }
+                }
+            }
+        };
+        mHandler.post(r);
     }
 
     /**
@@ -926,7 +1069,9 @@
         mUserVersion = USER_VERSION;
 
         Bundle restrictions = new Bundle();
-        mUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+        synchronized (mRestrictionsLock) {
+            mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+        }
 
         updateUserIdsLocked();
         initDefaultGuestRestrictions();
@@ -989,9 +1134,13 @@
             serializer.startTag(null, TAG_NAME);
             serializer.text(userInfo.name);
             serializer.endTag(null, TAG_NAME);
-            Bundle restrictions = mUserRestrictions.get(userInfo.id);
+            Bundle restrictions;
+            synchronized (mRestrictionsLock) {
+                restrictions = mBaseUserRestrictions.get(userInfo.id);
+            }
             if (restrictions != null) {
-                UserRestrictionsUtils.writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
+                UserRestrictionsUtils
+                        .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_USER);
 
@@ -1131,7 +1280,9 @@
             userInfo.guestToRemove = guestToRemove;
             userInfo.profileGroupId = profileGroupId;
             userInfo.restrictedProfileParentId = restrictedProfileParentId;
-            mUserRestrictions.append(id, restrictions);
+            synchronized (mRestrictionsLock) {
+                mBaseUserRestrictions.append(id, restrictions);
+            }
             return userInfo;
 
         } catch (IOException ioe) {
@@ -1333,7 +1484,9 @@
                     scheduleWriteUserLocked(userInfo);
                     updateUserIdsLocked();
                     Bundle restrictions = new Bundle();
-                    mUserRestrictions.append(userId, restrictions);
+                    synchronized (mRestrictionsLock) {
+                        mBaseUserRestrictions.append(userId, restrictions);
+                    }
                 }
             }
             mPm.newUserCreated(userId);
@@ -1616,25 +1769,6 @@
         }
     }
 
-    @Override
-    public void removeRestrictions() {
-        checkManageUsersPermission("remove restrictions");
-        final int userHandle = UserHandle.getCallingUserId();
-        removeRestrictionsForUser(userHandle, true);
-    }
-
-    private void removeRestrictionsForUser(final int userHandle, boolean unhideApps) {
-        synchronized (mPackagesLock) {
-            // Remove all user restrictions
-            setUserRestrictions(new Bundle(), userHandle);
-            // Remove any app restrictions
-            cleanAppRestrictions(userHandle);
-        }
-        if (unhideApps) {
-            unhideAllInstalledAppsForUser(userHandle);
-        }
-    }
-
     private void unhideAllInstalledAppsForUser(final int userHandle) {
         mHandler.post(new Runnable() {
             @Override
@@ -2062,7 +2196,10 @@
                 }
                 pw.println("    Restrictions:");
                 UserRestrictionsUtils.dumpRestrictions(
-                        pw, "      ", mUserRestrictions.get(user.id));
+                        pw, "      ", mBaseUserRestrictions.get(user.id));
+                pw.println("    Effective restrictions:");
+                UserRestrictionsUtils.dumpRestrictions(
+                        pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
             }
             pw.println();
             pw.println("Guest restrictions:");
@@ -2095,4 +2232,49 @@
     boolean isInitialized(int userId) {
         return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
     }
+
+    private class LocalService extends UserManagerInternal {
+
+        @Override
+        public Object getUserRestrictionsLock() {
+            return mRestrictionsLock;
+        }
+
+        @Override
+        @GuardedBy("mRestrictionsLock")
+        public void updateEffectiveUserRestrictionsRL(int userId) {
+            UserManagerService.this.updateEffectiveUserRestrictionsRL(userId);
+        }
+
+        @Override
+        @GuardedBy("mRestrictionsLock")
+        public void updateEffectiveUserRestrictionsForAllUsersRL() {
+            UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersRL();
+        }
+
+        @Override
+        public Bundle getBaseUserRestrictions(int userId) {
+            synchronized (mRestrictionsLock) {
+                return mBaseUserRestrictions.get(userId);
+            }
+        }
+
+        @Override
+        public void setBaseUserRestrictionsByDpmsForMigration(
+                int userId, Bundle baseRestrictions) {
+            synchronized (mRestrictionsLock) {
+                mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions));
+                invalidateEffectiveUserRestrictionsRL(userId);
+            }
+
+            synchronized (mPackagesLock) {
+                final UserInfo userInfo = mUsers.get(userId);
+                if (userInfo != null) {
+                    writeUserLocked(userInfo);
+                } else {
+                    Slog.w(LOG_TAG, "UserInfo not found for " + userId);
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index db1fd2e..23e3b35 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -86,7 +86,6 @@
             String tag) throws IOException {
         serializer.startTag(null, tag);
         for (String key : USER_RESTRICTIONS) {
-            //
             if (restrictions.getBoolean(key)
                     && !NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
                 serializer.attribute(null, key, "true");
@@ -105,6 +104,17 @@
         }
     }
 
+    public static void merge(Bundle dest, Bundle in) {
+        if (in == null) {
+            return;
+        }
+        for (String key : in.keySet()) {
+            if (in.getBoolean(key, false)) {
+                dest.putBoolean(key, true);
+            }
+        }
+    }
+
     public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
         boolean noneSet = true;
         if (restrictions != null) {
@@ -114,9 +124,11 @@
                     noneSet = false;
                 }
             }
-        }
-        if (noneSet) {
-            pw.println(prefix + "none");
+            if (noneSet) {
+                pw.println(prefix + "none");
+            }
+        } else {
+            pw.println(prefix + "null");
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 2365a45..eb0ca23 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -64,7 +64,6 @@
 import android.view.animation.PathInterpolator;
 import android.view.animation.ScaleAnimation;
 import android.view.animation.TranslateAnimation;
-import android.view.animation.TranslateYAnimation;
 
 import com.android.internal.util.DumpUtils.Dump;
 import com.android.server.AttributeCache;
@@ -175,7 +174,7 @@
     private Rect mTmpFromClipRect = new Rect();
     private Rect mTmpToClipRect = new Rect();
 
-    private final Rect mTmpStartRect = new Rect();
+    private final Rect mTmpRect = new Rect();
 
     private final static int APP_STATE_IDLE = 0;
     private final static int APP_STATE_READY = 1;
@@ -456,17 +455,19 @@
         return -startPos / denom;
     }
 
-    private Animation createScaleUpAnimationLocked(
-            int transit, boolean enter, int appWidth, int appHeight) {
-        Animation a = null;
-        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+    private Animation createScaleUpAnimationLocked(int transit, boolean enter,
+            Rect containingFrame) {
+        Animation a;
+        getDefaultNextAppTransitionStartRect(mTmpRect);
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
         if (enter) {
             // Entering app zooms out from the center of the initial rect.
-            float scaleW = mTmpStartRect.width() / (float) appWidth;
-            float scaleH = mTmpStartRect.height() / (float) appHeight;
+            float scaleW = mTmpRect.width() / (float) appWidth;
+            float scaleH = mTmpRect.height() / (float) appHeight;
             Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mTmpStartRect.left, scaleW),
-                    computePivot(mTmpStartRect.right, scaleH));
+                    computePivot(mTmpRect.left, scaleW),
+                    computePivot(mTmpRect.right, scaleH));
             scale.setInterpolator(mDecelerateInterpolator);
 
             Animation alpha = new AlphaAnimation(0, 1);
@@ -543,20 +544,20 @@
 
             final int appWidth = appFrame.width();
             final int appHeight = appFrame.height();
-            // mTmpStartRect will contain an area around the launcher icon that was pressed. We will
+            // mTmpRect will contain an area around the launcher icon that was pressed. We will
             // clip reveal from that area in the final area of the app.
-            getDefaultNextAppTransitionStartRect(mTmpStartRect);
+            getDefaultNextAppTransitionStartRect(mTmpRect);
 
             float t = 0f;
             if (appHeight > 0) {
-                t = (float) mTmpStartRect.left / appHeight;
+                t = (float) mTmpRect.left / appHeight;
             }
             int translationY = mClipRevealTranslationY + (int)(appHeight / 7f * t);
 
-            int centerX = mTmpStartRect.centerX();
-            int centerY = mTmpStartRect.centerY();
-            int halfWidth = mTmpStartRect.width() / 2;
-            int halfHeight = mTmpStartRect.height() / 2;
+            int centerX = mTmpRect.centerX();
+            int centerY = mTmpRect.centerY();
+            int halfWidth = mTmpRect.width() / 2;
+            int halfHeight = mTmpRect.height() / 2;
 
             // Clip third of the from size of launch icon, expand to full width/height
             Animation clipAnimLR = new ClipRectLRAnimation(
@@ -691,19 +692,19 @@
 
         float scaleW = appWidth / thumbWidth;
         float unscaledHeight = thumbHeight * scaleW;
-        getNextAppTransitionStartRect(taskId, mTmpStartRect);
-        float unscaledStartY = mTmpStartRect.top - (unscaledHeight - thumbHeight) / 2f;
+        getNextAppTransitionStartRect(taskId, mTmpRect);
+        float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f;
         if (mNextAppTransitionScaleUp) {
             // Animation up from the thumbnail to the full screen
             Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
-                    mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
+                    mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
             scale.setInterpolator(mTouchResponseInterpolator);
             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
             Animation alpha = new AlphaAnimation(1, 0);
             alpha.setInterpolator(mThumbnailFadeOutInterpolator);
             alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
             final float toX = appRect.left + appRect.width() / 2 -
-                    (mTmpStartRect.left + thumbWidth / 2);
+                    (mTmpRect.left + thumbWidth / 2);
             final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
             Animation translate = new TranslateAnimation(0, toX, 0, toY);
             translate.setInterpolator(mTouchResponseInterpolator);
@@ -718,7 +719,7 @@
         } else {
             // Animation down from the full screen to the thumbnail
             Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
-                    mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
+                    mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
             scale.setInterpolator(mTouchResponseInterpolator);
             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
             Animation alpha = new AlphaAnimation(0f, 1f);
@@ -746,14 +747,15 @@
      * activity that is leaving, and the activity that is entering.
      */
     Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
-            int appWidth, int appHeight, int orientation, int transit, Rect containingFrame,
-            Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow,
-            int taskId) {
+            int orientation, int transit, Rect containingFrame, Rect contentInsets,
+            @Nullable Rect surfaceInsets, boolean freeform, int taskId) {
         Animation a;
-        getDefaultNextAppTransitionStartRect(mTmpStartRect);
-        final int thumbWidthI = mTmpStartRect.width();
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
+        getDefaultNextAppTransitionStartRect(mTmpRect);
+        final int thumbWidthI = mTmpRect.width();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mTmpStartRect.height();
+        final int thumbHeightI = mTmpRect.height();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
 
         // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
@@ -762,8 +764,8 @@
 
         switch (thumbTransitState) {
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
-                if (resizedWindow) {
-                    a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
+                if (freeform) {
+                    a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
                             containingFrame, surfaceInsets, taskId);
                 } else {
                     mTmpFromClipRect.set(containingFrame);
@@ -794,8 +796,8 @@
                     mNextAppTransitionInsets.set(contentInsets);
 
                     Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
-                            computePivot(mTmpStartRect.left, scale),
-                            computePivot(mTmpStartRect.top, scale));
+                            computePivot(mTmpRect.left, scale),
+                            computePivot(mTmpRect.top, scale));
                     Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
                     Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
 
@@ -831,44 +833,49 @@
             }
             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
                 // App window scaling down from full screen
-                mTmpFromClipRect.set(containingFrame);
-                mTmpToClipRect.set(containingFrame);
-                // exclude top screen decor (status bar) region from the destination clip.
-                mTmpToClipRect.top = contentInsets.top;
-                if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-                    // In portrait, we scale the width and clip to the top/left square
-                    scale = thumbWidth / appWidth;
-                    scaledTopDecor = (int) (scale * contentInsets.top);
-                    int unscaledThumbHeight = (int) (thumbHeight / scale);
-                    mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
+                if (freeform) {
+                    a = createAspectScaledThumbnailExitFreeformAnimationLocked(
+                            containingFrame, surfaceInsets, taskId);
                 } else {
-                    // In landscape, we scale the height and clip to the top/left square. We only
-                    // scale the part that is not covered by status bar and the nav bar.
-                    scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom);
-                    scaledTopDecor = (int) (scale * contentInsets.top);
-                    int unscaledThumbWidth = (int) (thumbWidth / scale);
-                    mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth;
-                    // This removes the navigation bar from the last frame, so it better matches the
-                    // thumbnail. We need to do this explicitly in landscape, because in portrait we
-                    // already crop vertically.
-                    mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom;
+                    mTmpFromClipRect.set(containingFrame);
+                    mTmpToClipRect.set(containingFrame);
+                    // exclude top screen decor (status bar) region from the destination clip.
+                    mTmpToClipRect.top = contentInsets.top;
+                    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+                        // In portrait, we scale the width and clip to the top/left square
+                        scale = thumbWidth / appWidth;
+                        scaledTopDecor = (int) (scale * contentInsets.top);
+                        int unscaledThumbHeight = (int) (thumbHeight / scale);
+                        mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
+                    } else {
+                        // In landscape, we scale the height and clip to the top/left square. We only
+                        // scale the part that is not covered by status bar and the nav bar.
+                        scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom);
+                        scaledTopDecor = (int) (scale * contentInsets.top);
+                        int unscaledThumbWidth = (int) (thumbWidth / scale);
+                        mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth;
+                        // This removes the navigation bar from the last frame, so it better matches the
+                        // thumbnail. We need to do this explicitly in landscape, because in portrait we
+                        // already crop vertically.
+                        mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom;
+                    }
+
+                    mNextAppTransitionInsets.set(contentInsets);
+
+                    Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
+                            computePivot(mTmpRect.left, scale),
+                            computePivot(mTmpRect.top, scale));
+                    Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+                    Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
+
+                    AnimationSet set = new AnimationSet(true);
+                    set.addAnimation(clipAnim);
+                    set.addAnimation(scaleAnim);
+                    set.addAnimation(translateAnim);
+
+                    a = set;
+                    a.setZAdjustment(Animation.ZORDER_TOP);
                 }
-
-                mNextAppTransitionInsets.set(contentInsets);
-
-                Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
-                        computePivot(mTmpStartRect.left, scale),
-                        computePivot(mTmpStartRect.top, scale));
-                Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
-                Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
-
-                AnimationSet set = new AnimationSet(true);
-                set.addAnimation(clipAnim);
-                set.addAnimation(scaleAnim);
-                set.addAnimation(translateAnim);
-
-                a = set;
-                a.setZAdjustment(Animation.ZORDER_TOP);
                 break;
             }
             default:
@@ -881,27 +888,48 @@
                 mTouchResponseInterpolator);
     }
 
-    private Animation createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
-            Rect frame, @Nullable Rect surfaceInsets, int taskId) {
-        getNextAppTransitionStartRect(taskId, mTmpStartRect);
-        float width = frame.width();
-        float height = frame.height();
-        float scaleWidth = mTmpStartRect.width() / width;
-        float scaleHeight = mTmpStartRect.height() / height;
+    private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
+            @Nullable Rect surfaceInsets, int taskId) {
+        getNextAppTransitionStartRect(taskId, mTmpRect);
+        return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
+                true);
+    }
+
+    private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
+            @Nullable Rect surfaceInsets, int taskId) {
+        getNextAppTransitionStartRect(taskId, mTmpRect);
+        return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
+                false);
+    }
+
+    private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
+            Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
+        final float sourceWidth = sourceFrame.width();
+        final float sourceHeight = sourceFrame.height();
+        final float destWidth = destFrame.width();
+        final float destHeight = destFrame.height();
+        final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
+        final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
         AnimationSet set = new AnimationSet(true);
-        int surfaceInsetsHorizontal = surfaceInsets == null
+        final int surfaceInsetsH = surfaceInsets == null
                 ? 0 : surfaceInsets.left + surfaceInsets.right;
-        int surfaceInsetsVertical = surfaceInsets == null
+        final int surfaceInsetsV = surfaceInsets == null
                 ? 0 : surfaceInsets.top + surfaceInsets.bottom;
         // We want the scaling to happen from the center of the surface. In order to achieve that,
         // we need to account for surface insets that will be used to enlarge the surface.
-        ScaleAnimation scale = new ScaleAnimation(scaleWidth, 1, scaleHeight, 1,
-                (width + surfaceInsetsHorizontal) / 2, (height + surfaceInsetsVertical) / 2);
-        int fromX = mTmpStartRect.left + mTmpStartRect.width() / 2
-                - (frame.left + frame.width() / 2);
-        int fromY = mTmpStartRect.top + mTmpStartRect.height() / 2
-                - (frame.top + frame.height() / 2);
-        TranslateAnimation translation = new TranslateAnimation(fromX, 0, fromY, 0);
+        final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
+        final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
+        final ScaleAnimation scale = enter ?
+                new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
+                : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
+        final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
+        final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
+        final int destHCenter = destFrame.left + destFrame.width() / 2;
+        final int destVCenter = destFrame.top + destFrame.height() / 2;
+        final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
+        final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
+        final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
+                : new TranslateAnimation(0, fromX, 0, fromY);
         set.addAnimation(scale);
         set.addAnimation(translation);
         return set;
@@ -914,7 +942,7 @@
     Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit,
             Bitmap thumbnailHeader) {
         Animation a;
-        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        getDefaultNextAppTransitionStartRect(mTmpRect);
         final int thumbWidthI = thumbnailHeader.getWidth();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
         final int thumbHeightI = thumbnailHeader.getHeight();
@@ -925,8 +953,8 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                    computePivot(mTmpStartRect.left, 1 / scaleW),
-                    computePivot(mTmpStartRect.top, 1 / scaleH));
+                    computePivot(mTmpRect.left, 1 / scaleW),
+                    computePivot(mTmpRect.top, 1 / scaleH));
             scale.setInterpolator(mDecelerateInterpolator);
 
             Animation alpha = new AlphaAnimation(1, 0);
@@ -942,8 +970,8 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mTmpStartRect.left, 1 / scaleW),
-                    computePivot(mTmpStartRect.top, 1 / scaleH));
+                    computePivot(mTmpRect.left, 1 / scaleW),
+                    computePivot(mTmpRect.top, 1 / scaleH));
         }
 
         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
@@ -953,11 +981,13 @@
      * This animation is created when we are doing a thumbnail transition, for the activity that is
      * leaving, and the activity that is entering.
      */
-    Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
-            int appHeight, int transit, int taskId) {
+    Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame,
+            int transit, int taskId) {
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
         Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
         Animation a;
-        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        getDefaultNextAppTransitionStartRect(mTmpRect);
         final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
         final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
@@ -969,8 +999,8 @@
                 float scaleW = thumbWidth / appWidth;
                 float scaleH = thumbHeight / appHeight;
                 a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                        computePivot(mTmpStartRect.left, scaleW),
-                        computePivot(mTmpStartRect.top, scaleH));
+                        computePivot(mTmpRect.left, scaleW),
+                        computePivot(mTmpRect.top, scaleH));
                 break;
             }
             case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
@@ -997,8 +1027,8 @@
                 float scaleW = thumbWidth / appWidth;
                 float scaleH = thumbHeight / appHeight;
                 Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                        computePivot(mTmpStartRect.left, scaleW),
-                        computePivot(mTmpStartRect.top, scaleH));
+                        computePivot(mTmpRect.left, scaleW),
+                        computePivot(mTmpRect.top, scaleH));
 
                 Animation alpha = new AlphaAnimation(1, 0);
 
@@ -1016,18 +1046,22 @@
         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
     }
 
-    private Animation createRelaunchAnimation(int appWidth, int appHeight,
-            Rect containingFrame) {
+    private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) {
         getDefaultNextAppTransitionStartRect(mTmpFromClipRect);
         final int left = mTmpFromClipRect.left;
         final int top = mTmpFromClipRect.top;
         mTmpFromClipRect.offset(-left, -top);
-        mTmpToClipRect.set(0, 0, appWidth, appHeight);
+        // TODO: Isn't that strange that we ignore exact position of the containingFrame?
+        mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
         AnimationSet set = new AnimationSet(true);
         float fromWidth = mTmpFromClipRect.width();
         float toWidth = mTmpToClipRect.width();
         float fromHeight = mTmpFromClipRect.height();
-        float toHeight = mTmpToClipRect.height();
+        // While the window might span the whole display, the actual content will be cropped to the
+        // system decoration frame, for example when the window is docked. We need to take into
+        // account the visible height when constructing the animation.
+        float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
+        int translateAdjustment = 0;
         if (fromWidth <= toWidth && fromHeight <= toHeight) {
             // The final window is larger in both dimensions than current window (e.g. we are
             // maximizing), so we can simply unclip the new window and there will be no disappearing
@@ -1037,12 +1071,17 @@
             // The disappearing window has one larger dimension. We need to apply scaling, so the
             // first frame of the entry animation matches the old window.
             set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
+            // We might not be going exactly full screen, but instead be aligned under the status
+            // bar using cropping. We still need to account for the cropped part, which will also
+            // be scaled.
+            translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
         }
 
-        // We might not be going exactly full screen, but instead be aligned under the status bar.
-        // We need to take this into account when creating the translate animation.
+        // We animate the translation from the old position of the removed window, to the new
+        // position of the added window. The latter might not be full screen, for example docked for
+        // docked windows.
         TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
-                0, top - containingFrame.top, 0);
+                0, top - containingFrame.top - translateAdjustment, 0);
         set.addAnimation(translate);
         set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
         set.setZAdjustment(Animation.ZORDER_TOP);
@@ -1060,10 +1099,30 @@
                 && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL;
     }
 
+    /**
+     *
+     * @param frame These are the bounds of the window when it finishes the animation. This is where
+     *              the animation must usually finish in entrance animation, as the next frame will
+     *              display the window at these coordinates. In case of exit animation, this is
+     *              where the animation must start, as the frame before the animation is displaying
+     *              the window at these bounds.
+     * @param insets Knowing where the window will be positioned is not enough. Some parts of the
+     *               window might be obscured, usually by the system windows (status bar and
+     *               navigation bar) and we use content insets to convey that information. This
+     *               usually affects the animation aspects vertically, as the system decoration is
+     *               at the top and the bottom. For example when we animate from full screen to
+     *               recents, we want to exclude the covered parts, because they won't match the
+     *               thumbnail after the last frame is executed.
+     * @param surfaceInsets In rare situation the surface is larger than the content and we need to
+     *                      know about this to make the animation frames match. We currently use
+     *                      this for freeform windows, which have larger surfaces to display
+     *                      shadows. When we animate them from recents, we want to match the content
+     *                      to the recents thumbnail and hence need to account for the surface being
+     *                      bigger.
+     */
     Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
-            @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction,
-            boolean resizedWindow, int taskId) {
+            int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets,
+            boolean isVoiceInteraction, boolean freeform, int taskId) {
         Animation a;
         if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
                 || transit == TRANSIT_TASK_OPEN
@@ -1086,7 +1145,7 @@
                     + " anim=" + a + " transit=" + appTransitionToString(transit)
                     + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
         } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
-            a = createRelaunchAnimation(appWidth, appHeight, containingFrame);
+            a = createRelaunchAnimation(frame, insets);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                     + " anim=" + a + " nextAppTransition=" + mNextAppTransition
@@ -1108,14 +1167,14 @@
                     + " transit=" + appTransitionToString(transit)
                     + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
-            a = createClipRevealAnimationLocked(transit, enter, appFrame);
+            a = createClipRevealAnimationLocked(transit, enter, frame);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                             + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
                             + " transit=" + appTransitionToString(transit)
                             + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
-            a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
+            a = createScaleUpAnimationLocked(transit, enter, frame);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                     + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
@@ -1126,7 +1185,7 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
             a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
-                    appWidth, appHeight, transit, taskId);
+                    frame, transit, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
@@ -1140,8 +1199,8 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
             a = createAspectScaledThumbnailEnterExitAnimationLocked(
-                    getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit,
-                    containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId);
+                    getThumbnailTransitionState(enter), orientation, transit, frame,
+                    insets, surfaceInsets, freeform, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
@@ -1458,15 +1517,15 @@
                         pw.print(Integer.toHexString(mNextAppTransitionInPlace));
                 break;
             case NEXT_TRANSIT_TYPE_SCALE_UP: {
-                getDefaultNextAppTransitionStartRect(mTmpStartRect);
+                getDefaultNextAppTransitionStartRect(mTmpRect);
                 pw.print(prefix); pw.print("mNextAppTransitionStartX=");
-                        pw.print(mTmpStartRect.left);
+                        pw.print(mTmpRect.left);
                         pw.print(" mNextAppTransitionStartY=");
-                        pw.println(mTmpStartRect.top);
+                        pw.println(mTmpRect.top);
                 pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
-                        pw.print(mTmpStartRect.width());
+                        pw.print(mTmpRect.width());
                         pw.print(" mNextAppTransitionStartHeight=");
-                        pw.println(mTmpStartRect.height());
+                        pw.println(mTmpRect.height());
                 break;
             }
             case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index bf7063f..a61e83f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -300,7 +300,8 @@
                 return false;
             }
 
-            if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
+            if ((mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface
+                    || animating || mAppToken.startingDisplayed)
                     && animation != null) {
                 if (!animating) {
                     if (DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5a1ed58..c5bd3a7 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -17,6 +17,9 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerService.TAG;
 
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.wm.WindowManagerService.H;
@@ -52,6 +55,13 @@
 
     final boolean voiceInteraction;
 
+    // Whether the window has a saved surface from last pause, which can be
+    // used to start an entering animation earlier.
+    boolean mHasSavedSurface;
+
+    // Whether we're performing an entering animation with a saved surface.
+    boolean mAnimatingWithSavedSurface;
+
     Task mTask;
     boolean appFullscreen;
     int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -114,11 +124,14 @@
     // This application will have its window replaced due to relaunch. This allows window manager
     // to differentiate between simple removal of a window and replacement. In the latter case it
     // will preserve the old window until the new one is drawn.
-    boolean mReplacingWindow;
+    boolean mWillReplaceWindow;
     // If true, the replaced window was already requested to be removed.
     boolean mReplacingRemoveRequested;
     // Whether the replacement of the window should trigger app transition animation.
     boolean mAnimateReplacingWindow;
+    // If not null, the window that will be used to replace the old one. This is being set when
+    // the window is added and unset when this window reports its first draw.
+    WindowState mReplacingWindow;
 
     AppWindowToken(WindowManagerService _service, IApplicationToken _token,
             boolean _voiceInteraction) {
@@ -257,10 +270,13 @@
         final int N = allAppWindows.size();
         for (int i=0; i<N; i++) {
             WindowState win = allAppWindows.get(i);
+            // If we're animating with a saved surface, we're already visible.
+            // Return true so that the alpha doesn't get cleared.
             if (!win.mAppFreezing
-                    && (win.mViewVisibility == View.VISIBLE ||
-                        (win.mWinAnimator.isAnimating() &&
-                                !service.mAppTransition.isTransitionSet()))
+                    && (win.mViewVisibility == View.VISIBLE
+                    || mAnimatingWithSavedSurface
+                    || (win.mWinAnimator.isAnimating() &&
+                            !service.mAppTransition.isTransitionSet()))
                     && !win.mDestroying && win.isDrawnLw()) {
                 return true;
             }
@@ -283,6 +299,52 @@
         }
     }
 
+    /**
+     * Checks whether we should save surfaces for this app.
+     *
+     * @return true if the surfaces should be saved, false otherwise.
+     */
+    boolean shouldSaveSurface() {
+        // We want to save surface if the app's windows are "allDrawn", or if we're
+        // currently animating with save surfaces. (If the app didn't even finish
+        // drawing when the user exits, but we have a saved surface from last time,
+        // we still want to keep that surface.)
+        mHasSavedSurface = allDrawn || mAnimatingWithSavedSurface;
+        if (mHasSavedSurface) {
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "Saving surface: " + this);
+            return true;
+        }
+        return false;
+    }
+
+    void restoreSavedSurfaces() {
+        if (!mHasSavedSurface) {
+            return;
+        }
+
+        if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                "Restoring saved surfaces: " + this + ", allDrawn=" + allDrawn);
+
+        mHasSavedSurface = false;
+        mAnimatingWithSavedSurface = true;
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            WindowState ws = windows.get(i);
+            ws.mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
+        }
+    }
+
+    void destroySavedSurfaces() {
+        if (mHasSavedSurface) {
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "Destroying saved surface: " + this);
+            for (int i = windows.size() - 1; i >= 0; i--) {
+                final WindowState win = windows.get(i);
+                win.mWinAnimator.destroySurfaceLocked();
+            }
+        }
+    }
+
     @Override
     void removeAllWindows() {
         for (int winNdx = allAppWindows.size() - 1; winNdx >= 0;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 39479c1..fab8ee5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -18,7 +18,7 @@
 
 import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
-
+import static android.app.ActivityManager.PINNED_STACK_ID;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
@@ -243,10 +243,30 @@
     }
 
     void moveStack(TaskStack stack, boolean toTop) {
+        if (stack.mStackId == PINNED_STACK_ID && !toTop) {
+            // Pinned stack is always-on-top silly...
+            Slog.w(TAG, "Ignoring move of always-on-top stack=" + stack + " to bottom");
+            return;
+        }
+
         if (!mStacks.remove(stack)) {
             Slog.wtf(TAG, "moving stack that was not added: " + stack, new Throwable());
         }
-        mStacks.add(toTop ? mStacks.size() : 0, stack);
+
+        int addIndex = toTop ? mStacks.size() : 0;
+
+        if (toTop
+                && mService.isStackVisibleLocked(PINNED_STACK_ID)
+                && stack.mStackId != PINNED_STACK_ID) {
+            // The pinned stack is always the top most stack (always-on-top) when it is visible.
+            // So, stack is moved just below the pinned stack.
+            addIndex--;
+            TaskStack topStack = mStacks.get(addIndex);
+            if (topStack.mStackId != PINNED_STACK_ID) {
+                throw new IllegalStateException("Pinned stack isn't top stack??? " + mStacks);
+            }
+        }
+        mStacks.add(addIndex, stack);
     }
 
     void detachStack(TaskStack stack) {
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 2d87123..04cba81 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -87,6 +89,8 @@
         final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
         final int width = landscape ? mDividerWidth : MATCH_PARENT;
         final int height = landscape ? MATCH_PARENT : mDividerWidth;
+        view.setPointerShape(
+                landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW);
         WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 width, height, TYPE_DOCK_DIVIDER,
                 FLAG_TOUCHABLE_WHEN_WAKING | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index f5e97e5..f35ea66 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -77,7 +77,6 @@
         mSurfaceControl = surface;
         mFlags = flags;
         mLocalWin = localWin;
-        mUid = Binder.getCallingUid();
         mNotifiedWindows = new ArrayList<WindowState>();
     }
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 6c391ad..5f911c3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -17,6 +17,8 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerService.DEBUG_INPUT;
 import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
 import android.app.ActivityManagerNative;
 import android.graphics.Rect;
@@ -179,7 +181,17 @@
         if (modal && child.mAppToken != null) {
             // Limit the outer touch to the activity stack region.
             flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-            child.getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
+            // If this is a modal window we need to dismiss it if it's not full screen and the touch
+            // happens outside of the frame that displays the content. This means we need to
+            // intercept touches outside of that window. The dim layer user associated with the
+            // window (task or stack) will give us the good bounds, as they would be used to display
+            // the dim layer.
+            final DimLayer.DimLayerUser dimLayerUser = child.getDimLayerUser();
+            if (dimLayerUser != null) {
+                dimLayerUser.getBounds(mTmpRect);
+            } else {
+                child.getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
+            }
             inputWindowHandle.touchableRegion.set(mTmpRect);
         } else {
             // Not modal or full screen modal
@@ -227,7 +239,9 @@
             inputWindowHandle.scaleFactor = 1;
         }
 
-
+        if (DEBUG_INPUT) {
+            Slog.d(WindowManagerService.TAG, "addInputWindowHandle: " + inputWindowHandle);
+        }
         addInputWindowHandleLw(inputWindowHandle);
     }
 
@@ -428,7 +442,7 @@
      * Layer assignment is assumed to be complete by the time this is called.
      */
     public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
-        if (WindowManagerService.DEBUG_FOCUS_LIGHT || WindowManagerService.DEBUG_INPUT) {
+        if (DEBUG_FOCUS_LIGHT || DEBUG_INPUT) {
             Slog.d(WindowManagerService.TAG, "Input focus has changed to " + newWindow);
         }
 
@@ -464,7 +478,7 @@
 
     public void pauseDispatchingLw(WindowToken window) {
         if (! window.paused) {
-            if (WindowManagerService.DEBUG_INPUT) {
+            if (DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window);
             }
 
@@ -475,7 +489,7 @@
 
     public void resumeDispatchingLw(WindowToken window) {
         if (window.paused) {
-            if (WindowManagerService.DEBUG_INPUT) {
+            if (DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window);
             }
 
@@ -486,7 +500,7 @@
 
     public void freezeInputDispatchingLw() {
         if (! mInputDispatchFrozen) {
-            if (WindowManagerService.DEBUG_INPUT) {
+            if (DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Freezing input dispatching");
             }
 
@@ -497,7 +511,7 @@
 
     public void thawInputDispatchingLw() {
         if (mInputDispatchFrozen) {
-            if (WindowManagerService.DEBUG_INPUT) {
+            if (DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Thawing input dispatching");
             }
 
@@ -508,7 +522,7 @@
 
     public void setEventDispatchingLw(boolean enabled) {
         if (mInputDispatchEnabled != enabled) {
-            if (WindowManagerService.DEBUG_INPUT) {
+            if (DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled);
             }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6527881..ad44196 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.HOME_STACK_ID;
 import static android.app.ActivityManager.PINNED_STACK_ID;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static com.android.server.wm.WindowManagerService.TAG;
@@ -324,6 +325,10 @@
         return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
     }
 
+    boolean inHomeStack() {
+        return mStack != null && mStack.mStackId == HOME_STACK_ID;
+    }
+
     boolean inFreeformWorkspace() {
         return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
     }
@@ -337,6 +342,11 @@
         return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null;
     }
 
+    AppWindowToken getTopAppWindowToken() {
+        final int tokensCount = mAppTokens.size();
+        return tokensCount > 0 ? mAppTokens.get(tokensCount - 1) : null;
+    }
+
     @Override
     public boolean isFullscreen() {
         if (useCurrentBounds()) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9b3d478..df664bd 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -409,7 +409,7 @@
             final boolean dockedOnTopOrLeft = WindowManagerService.sDockedStackCreateMode
                     == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
             getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
-                    mDisplayContent.mDividerControllerLocked.getWidthAdjustment(),
+                    mDisplayContent.mDividerControllerLocked.getWidth(),
                     dockedOnTopOrLeft);
         }
 
@@ -459,7 +459,7 @@
         dockedStack.getRawBounds(mTmpRect2);
         final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
         getStackDockedModeBounds(mTmpRect, outBounds, mStackId, mTmpRect2,
-                mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), dockedOnTopOrLeft);
+                mDisplayContent.mDividerControllerLocked.getWidth(), dockedOnTopOrLeft);
 
     }
 
@@ -469,13 +469,13 @@
      * @param outBounds Output bounds that should be used for the stack.
      * @param stackId Id of stack we are calculating the bounds for.
      * @param dockedBounds Bounds of the docked stack.
-     * @param adjustment Additional adjustment to make to the output bounds close to the side of the
-     *                   dock.
-     * @param dockOntopOrLeft If the docked stack is on the top or left side of the screen.
+     * @param dockDividerWidth We need to know the width of the divider make to the output bounds
+     *                         close to the side of the dock.
+     * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen.
      */
     private static void getStackDockedModeBounds(
-            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int adjustment,
-            boolean dockOntopOrLeft) {
+            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth,
+            boolean dockOnTopOrLeft) {
         final boolean dockedStack = stackId == DOCKED_STACK_ID;
         final boolean splitHorizontally = displayRect.width() > displayRect.height();
 
@@ -484,34 +484,34 @@
             // The initial bounds of the docked stack when it is created half the screen space and
             // its bounds can be adjusted after that. The bounds of all other stacks are adjusted
             // to occupy whatever screen space the docked stack isn't occupying.
-            if (dockOntopOrLeft) {
+            if (dockOnTopOrLeft) {
                 if (splitHorizontally) {
-                    outBounds.right = displayRect.centerX() - adjustment;
+                    outBounds.right = displayRect.centerX() - dockDividerWidth / 2;
                 } else {
-                    outBounds.bottom = displayRect.centerY() - adjustment;
+                    outBounds.bottom = displayRect.centerY() - dockDividerWidth / 2;
                 }
             } else {
                 if (splitHorizontally) {
-                    outBounds.left = displayRect.centerX() + adjustment;
+                    outBounds.left = displayRect.centerX() + dockDividerWidth / 2;
                 } else {
-                    outBounds.top = displayRect.centerY() + adjustment;
+                    outBounds.top = displayRect.centerY() + dockDividerWidth / 2;
                 }
             }
             return;
         }
 
         // Other stacks occupy whatever space is left by the docked stack.
-        if (!dockOntopOrLeft) {
+        if (!dockOnTopOrLeft) {
             if (splitHorizontally) {
-                outBounds.right = dockedBounds.left - adjustment;
+                outBounds.right = dockedBounds.left - dockDividerWidth;
             } else {
-                outBounds.bottom = dockedBounds.top - adjustment;
+                outBounds.bottom = dockedBounds.top - dockDividerWidth;
             }
         } else {
             if (splitHorizontally) {
-                outBounds.left = dockedBounds.right + adjustment;
+                outBounds.left = dockedBounds.right + dockDividerWidth;
             } else {
-                outBounds.top = dockedBounds.bottom + adjustment;
+                outBounds.top = dockedBounds.bottom + dockDividerWidth;
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 928a117..f4a4140 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -31,6 +31,7 @@
 
 import android.content.Context;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -413,7 +414,7 @@
 
             final AppWindowToken atoken = win.mAppToken;
             if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
-                if (atoken == null || atoken.allDrawn) {
+                if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface) {
                     if (winAnimator.performShowLocked()) {
                         setPendingLayoutChanges(displayId,
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
@@ -731,8 +732,15 @@
             mWindowPlacerLocked.requestTraversal();
         }
 
+        if (mAnimating && !wasAnimating && Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+        }
+
         if (!mAnimating && wasAnimating) {
             mWindowPlacerLocked.requestTraversal();
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+            }
         }
 
         mService.destroyPreservedSurfaceLocked();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cd5fbb0..140fbaf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1327,8 +1327,14 @@
             addAttachedWindowToListLocked(win, addToToken);
         }
 
-        if (win.mAppToken != null && addToToken) {
-            win.mAppToken.allAppWindows.add(win);
+        final AppWindowToken appToken = win.mAppToken;
+        if (appToken != null) {
+            if (addToToken) {
+                appToken.allAppWindows.add(win);
+            }
+            if (appToken.mWillReplaceWindow) {
+                appToken.mReplacingWindow = win;
+            }
         }
     }
 
@@ -2053,7 +2059,7 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) {
+        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
             return;
         }
         atoken.allDrawn = false;
@@ -2157,6 +2163,8 @@
                 + " isAnimating=" + win.mWinAnimator.isAnimating()
                 + " app-animation="
                 + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
+                + " mWillReplaceWindow="
+                + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false)
                 + " inPendingTransaction="
                 + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
                 + " mDisplayFrozen=" + mDisplayFrozen);
@@ -2168,8 +2176,8 @@
         // animation wouldn't be seen.
         if (win.mHasSurface && okToDisplay()) {
             final AppWindowToken appToken = win.mAppToken;
-            if (appToken != null && appToken.mReplacingWindow) {
-                // This window is going to be replaced. We need to kepp it around until the new one
+            if (appToken != null && appToken.mWillReplaceWindow) {
+                // This window is going to be replaced. We need to keep it around until the new one
                 // gets added, then we will get rid of this one.
                 if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is "
                         + "added");
@@ -2629,6 +2637,11 @@
                     }
                 }
                 dragResizing = win.isDragResizing();
+                if (win.isAnimatingWithSavedSurface()) {
+                    // If we're animating with a saved surface now, request client to report draw.
+                    // We still need to know when the real thing is drawn.
+                    toBeDisplayed = true;
+                }
                 try {
                     if (!win.mHasSurface) {
                         surfaceChanged = true;
@@ -2679,9 +2692,14 @@
                 if (winAnimator.mSurfaceControl != null) {
                     if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
                             + ": mExiting=" + win.mExiting);
+                    // If we are using a saved surface to do enter animation, just let the
+                    // animation run and don't destroy the surface. This could happen when
+                    // the app sets visibility to invisible for the first time after resume,
+                    // or when the user exits immediately after a resume. In both cases, we
+                    // don't want to destroy the saved surface.
                     // If we are not currently running the exit animation, we
                     // need to see about starting one.
-                    if (!win.mExiting) {
+                    if (!win.mExiting && !win.isAnimatingWithSavedSurface()) {
                         surfaceChanged = true;
                         // Try starting an animation; if there isn't one, we
                         // can destroy the surface right away.
@@ -2707,7 +2725,9 @@
                             if (mInputMethodWindow == win) {
                                 mInputMethodWindow = null;
                             }
-                            winAnimator.destroySurfaceLocked();
+                            if (!win.shouldSaveSurface()) {
+                                winAnimator.destroySurfaceLocked();
+                            }
                         }
                         //TODO (multidisplay): Magnification is supported only for the default
                         if (mAccessibilityController != null
@@ -2833,7 +2853,8 @@
         try {
             synchronized (mWindowMap) {
                 WindowState win = windowForClientLocked(session, client, false);
-                if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win);
+                if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win + " mDrawState="
+                        + (win != null ? win.mWinAnimator.drawStateToString() : "null"));
                 if (win != null && win.mWinAnimator.finishDrawingLocked()) {
                     if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
                         getDefaultDisplayContentLocked().pendingLayoutChanges |=
@@ -2862,46 +2883,44 @@
                     "applyAnimation: atoken=" + atoken);
 
             // Determine the visible rect to calculate the thumbnail clip
-            WindowState win = atoken.findMainWindow();
-            Rect containingFrame = new Rect(0, 0, width, height);
-            Rect contentInsets = new Rect();
-            Rect appFrame = new Rect(0, 0, width, height);
+            final WindowState win = atoken.findMainWindow();
+            final Rect frame = new Rect(0, 0, width, height);
+            final Rect insets = new Rect();
             Rect surfaceInsets = null;
             final boolean fullscreen = win != null && win.isFullscreen(width, height);
             final boolean freeform = win != null && win.inFreeformWorkspace();
+            final boolean docked = win != null && win.inDockedWorkspace();
             if (win != null) {
                 // Containing frame will usually cover the whole screen, including dialog windows.
                 // For freeform workspace windows it will not cover the whole screen and it also
                 // won't exactly match the final freeform window frame (e.g. when overlapping with
                 // the status bar). In that case we need to use the final frame.
                 if (freeform) {
-                    containingFrame.set(win.mFrame);
+                    frame.set(win.mFrame);
                 } else {
-                    containingFrame.set(win.mContainingFrame);
+                    frame.set(win.mContainingFrame);
                 }
                 surfaceInsets = win.getAttrs().surfaceInsets;
-                if (fullscreen) {
+                if (fullscreen || docked) {
                     // For fullscreen windows use the window frames and insets to set the thumbnail
-                    // clip. For none-fullscreen windows we use the app display region so the clip
+                    // clip. For non-fullscreen windows we use the app display region so the clip
                     // isn't affected by the window insets.
-                    contentInsets.set(win.mContentInsets);
-                    appFrame.set(win.mFrame);
-                } else {
-                    appFrame.set(containingFrame);
+                    insets.set(win.mContentInsets);
                 }
             }
 
-            final int containingWidth = containingFrame.width();
-            final int containingHeight = containingFrame.height();
             if (atoken.mLaunchTaskBehind) {
                 // Differentiate the two animations. This one which is briefly on the screen
                 // gets the !enter animation, and the other activity which remains on the
                 // screen gets the enter animation. Both appear in the mOpeningApps set.
                 enter = false;
             }
-            Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth,
-                    containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets,
-                    surfaceInsets, appFrame, isVoiceInteraction, freeform, atoken.mTask.mTaskId);
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "Loading animation for app transition."
+                    + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+                    + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+            Animation a = mAppTransition.loadAnimation(lp, transit, enter,
+                    mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction,
+                    freeform, atoken.mTask.mTaskId);
             if (a != null) {
                 if (DEBUG_ANIM) {
                     RuntimeException e = null;
@@ -2911,6 +2930,8 @@
                     }
                     Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e);
                 }
+                final int containingWidth = frame.width();
+                final int containingHeight = frame.height();
                 atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
                         mAppTransition.canSkipFirstFrame());
             }
@@ -3262,10 +3283,8 @@
             }
         }
 
-        final TaskStack dockedStack = mStackIdToStack.get(DOCKED_STACK_ID);
-        final TaskStack freeformStack = mStackIdToStack.get(FREEFORM_WORKSPACE_STACK_ID);
-        if ((dockedStack != null && dockedStack.isVisibleLocked())
-                || (freeformStack != null && freeformStack.isVisibleLocked())) {
+        if (isStackVisibleLocked(DOCKED_STACK_ID)
+                || isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
             // We don't let app affect the system orientation when in freeform or docked mode since
             // they don't occupy the entire display and their request can conflict with other apps.
             return SCREEN_ORIENTATION_UNSPECIFIED;
@@ -3909,7 +3928,7 @@
         // transition animation
         // * or this is an opening app and windows are being replaced.
         if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
-                (visible && wtoken.mReplacingWindow)) {
+                (visible && wtoken.mWillReplaceWindow)) {
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(
                 TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -4496,6 +4515,11 @@
         }
     }
 
+    boolean isStackVisibleLocked(int stackId) {
+        final TaskStack stack = mStackIdToStack.get(stackId);
+        return (stack != null && stack.isVisibleLocked());
+    }
+
     public void setDockedStackCreateMode(int mode) {
         synchronized (mWindowMap) {
             sDockedStackCreateMode = mode;
@@ -4657,7 +4681,7 @@
                 throw new IllegalArgumentException("resizeStack: stackId " + stackId
                         + " not found.");
             }
-            if (stack.setBounds(bounds, configs, taskBounds)) {
+            if (stack.setBounds(bounds, configs, taskBounds) && stack.isVisibleLocked()) {
                 stack.resizeWindows();
                 stack.getDisplayContent().layoutNeeded = true;
                 mWindowPlacerLocked.performSurfacePlacement();
@@ -4710,6 +4734,26 @@
         }
     }
 
+    /**
+     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
+     * performance, only one layout pass should be done. This can be called multiple times, and
+     * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout}
+     */
+    public void deferSurfaceLayout() {
+        synchronized (mWindowMap) {
+            mWindowPlacerLocked.deferLayout();
+        }
+    }
+
+    /**
+     * Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()}
+     */
+    public void continueSurfaceLayout() {
+        synchronized (mWindowMap) {
+            mWindowPlacerLocked.continueLayout();
+        }
+    }
+
     public void getTaskBounds(int taskId, Rect bounds) {
         synchronized (mWindowMap) {
             Task task = mTaskIdToTask.get(taskId);
@@ -5396,8 +5440,8 @@
                 if (visible) {
                     // TODO(multi-display): support multiple displays
                     if (mCircularDisplayMask == null) {
-                        int screenOffset = mContext.getResources().getDimensionPixelSize(
-                                com.android.internal.R.dimen.circular_display_mask_offset);
+                        int screenOffset = mContext.getResources().getInteger(
+                                com.android.internal.R.integer.config_windowOutsetBottom);
                         int maskThickness = mContext.getResources().getDimensionPixelSize(
                                 com.android.internal.R.dimen.circular_display_mask_thickness);
 
@@ -6070,6 +6114,10 @@
         final WindowList windows = displayContent.getWindowList();
         for (int i = windows.size() - 1; i >= 0; i--) {
             WindowState w = windows.get(i);
+            // Discard surface after orientation change, these can't be reused.
+            if (w.mAppToken != null) {
+                w.mAppToken.destroySavedSurfaces();
+            }
             if (w.mHasSurface) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG, "Set mOrientationChanging of " + w);
                 w.mOrientationChanging = true;
@@ -6972,7 +7020,7 @@
                     + " asbinder=" + window.asBinder());
         }
 
-        final int callerPid = Binder.getCallingPid();
+        final int callerUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         IBinder token = null;
 
@@ -6997,6 +7045,7 @@
                         final IBinder winBinder = window.asBinder();
                         token = new Binder();
                         mDragState = new DragState(this, token, surface, flags, winBinder);
+                        mDragState.mUid = callerUid;
                         token = mDragState.mToken = new Binder();
 
                         // 5 second timeout for this window to actually begin the drag
@@ -8346,7 +8395,7 @@
                 final int numTokens = tokens.size();
                 for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
                     final AppWindowToken wtoken = tokens.get(tokenNdx);
-                    if (wtoken.mIsExiting && !wtoken.mReplacingWindow) {
+                    if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) {
                         continue;
                     }
                     i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8416,7 +8465,8 @@
             } else if (wtoken != null) {
                 winAnimator.mAnimLayer =
                         w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
-                if (wtoken.mReplacingWindow && wtoken.mAnimateReplacingWindow) {
+                if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
+                        wtoken.mReplacingWindow != w) {
                     // We know that we will be animating a relaunching window in the near future,
                     // which will receive a z-order increase. We want the replaced window to
                     // immediately receive the same treatment, e.g. to be above the dock divider.
@@ -8723,12 +8773,14 @@
                         } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) {
                             Slog.w(TAG, "LEAKED SURFACE (app token hidden): "
                                     + ws + " surface=" + wsa.mSurfaceControl
-                                    + " token=" + ws.mAppToken);
+                                    + " token=" + ws.mAppToken
+                                    + " saved=" + ws.mAppToken.mHasSavedSurface);
                             if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
                             wsa.mSurfaceControl.destroy();
                             wsa.mSurfaceShown = false;
                             wsa.mSurfaceControl = null;
                             ws.mHasSurface = false;
+                            ws.mAppToken.mHasSavedSurface = false;
                             leakedSurface = true;
                         }
                     }
@@ -9962,7 +10014,9 @@
                 Slog.w(TAG, "Attempted to set replacing window on non-existing app token " + token);
                 return;
             }
-            appWindowToken.mReplacingWindow = true;
+            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Marking app token " + appWindowToken
+                    + " as replacing window.");
+            appWindowToken.mWillReplaceWindow = true;
             appWindowToken.mAnimateReplacingWindow = animate;
         }
     }
@@ -10168,8 +10222,7 @@
         @Override
         public boolean isStackVisible(int stackId) {
             synchronized (mWindowMap) {
-                final TaskStack stack = mStackIdToStack.get(stackId);
-                return (stack != null && stack.isVisibleLocked());
+                return WindowManagerService.this.isStackVisibleLocked(stackId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7ea64f1..1d2cb75 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,6 +28,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
@@ -35,6 +38,7 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.graphics.Point;
 import android.os.PowerManager;
@@ -55,7 +59,6 @@
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -446,7 +449,8 @@
                     + WindowManagerService.TYPE_LAYER_OFFSET;
             mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
             mAttachedWindow = attachedWindow;
-            if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow);
+            if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to "
+                    + mAttachedWindow);
 
             final WindowList childWindows = mAttachedWindow.mChildWindows;
             final int numChildWindows = childWindows.size();
@@ -552,7 +556,7 @@
     @Override
     public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
             Rect osf) {
-        if (mAppToken != null && mAppToken.mReplacingWindow
+        if (mAppToken != null && mAppToken.mWillReplaceWindow
                 && (mExiting || !mAppToken.mReplacingRemoveRequested)) {
             // This window is being replaced and either already got information that it's being
             // removed or we are still waiting for some information. Because of this we don't
@@ -1307,10 +1311,12 @@
 
     void maybeRemoveReplacedWindow() {
         AppWindowToken token = mAppToken;
-        if (token != null && token.mReplacingWindow) {
-            token.mReplacingWindow = false;
+        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this) {
+            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
+            token.mWillReplaceWindow = false;
             token.mAnimateReplacingWindow = false;
             token.mReplacingRemoveRequested = false;
+            token.mReplacingWindow = null;
             for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
                 WindowState win = token.allAppWindows.get(i);
                 if (win.mExiting) {
@@ -1501,6 +1507,28 @@
         return mExiting || (mService.mClosingApps.contains(mAppToken));
     }
 
+    boolean isAnimatingWithSavedSurface() {
+        return mAppToken != null && mAppToken.mAnimatingWithSavedSurface;
+    }
+
+    boolean shouldSaveSurface() {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            // Don't save surfaces on Svelte devices.
+            return false;
+        }
+
+        Task task = getTask();
+        if (task == null || task.inHomeStack()
+                || task.getTopAppWindowToken() != mAppToken) {
+            // Don't save surfaces for home stack apps. These usually resume and draw
+            // first frame very fast. Saving surfaces are mostly a waste of memory.
+            // Don't save if the window is not the topmost window.
+            return false;
+        }
+
+        return mAppToken.shouldSaveSurface();
+    }
+
     @Override
     public boolean isDefaultDisplay() {
         final DisplayContent displayContent = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1754123..80f1094 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -38,7 +38,6 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Debug;
 import android.os.RemoteException;
@@ -526,6 +525,12 @@
             Slog.v(TAG, "Finishing drawing window " + mWin + ": mDrawState="
                     + drawStateToString());
         }
+        if (mWin.mAppToken != null) {
+            // App has drawn something to its windows, we're no longer animating with
+            // the saved surfaces. If the user exits now, we only want to save again
+            // if allDrawn is true.
+            mWin.mAppToken.mAnimatingWithSavedSurface = false;
+        }
         if (mDrawState == DRAW_PENDING) {
             if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
                 Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in "
@@ -536,6 +541,7 @@
             mDrawState = COMMIT_DRAW_PENDING;
             return true;
         }
+
         return false;
     }
 
@@ -555,7 +561,8 @@
         mDrawState = READY_TO_SHOW;
         boolean result = false;
         final AppWindowToken atoken = mWin.mAppToken;
-        if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
+        if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface ||
+                mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
             result = performShowLocked();
         }
         if (mDestroyPreservedSurfaceUponRedraw && result) {
@@ -996,12 +1003,16 @@
     }
 
     void destroySurfaceLocked() {
-        if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) {
-            mWin.mAppToken.startingDisplayed = false;
+        final AppWindowToken wtoken = mWin.mAppToken;
+        if (wtoken != null) {
+            wtoken.mHasSavedSurface = false;
+            wtoken.mAnimatingWithSavedSurface = false;
+            if (mWin == wtoken.startingWindow) {
+                wtoken.startingDisplayed = false;
+            }
         }
 
         if (mSurfaceControl != null) {
-
             int i = mWin.mChildWindows.size();
             while (i > 0) {
                 i--;
@@ -1428,7 +1439,7 @@
         // living in a different stack. If we suddenly crop it to the new stack bounds, it might
         // get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
         // gets removed. The window that will replace it will abide them.
-        if (appToken != null && appToken.mCropWindowsToStack && !appToken.mReplacingWindow) {
+        if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
             TaskStack stack = w.getTask().mStack;
             stack.getBounds(mTmpStackBounds);
             // When we resize we use the big surface approach, which means we can't trust the
@@ -1854,8 +1865,7 @@
                 }
             }
 
-            if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING
-                    && mWin.mAppToken != null) {
+            if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
                 mWin.mAppToken.firstWindowDrawn = true;
 
                 if (mWin.mAppToken.startingData != null) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 0c004b2..aca0f5b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -17,6 +17,7 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.WindowManagerService.DEBUG;
 import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS;
@@ -111,13 +112,34 @@
     private int mPreferredModeId = 0;
 
     private boolean mTraversalScheduled;
+    private int mDeferDepth = 0;
 
     public WindowSurfacePlacer(WindowManagerService service) {
         mService = service;
         mWallpaperControllerLocked = mService.mWallpaperControllerLocked;
     }
 
+    /**
+     * See {@link WindowManagerService#deferSurfaceLayout()}
+     */
+    void deferLayout() {
+        mDeferDepth++;
+    }
+
+    /**
+     * See {@link WindowManagerService#continueSurfaceLayout()}
+     */
+    void continueLayout() {
+        mDeferDepth--;
+        if (mDeferDepth <= 0) {
+            performSurfacePlacement();
+        }
+    }
+
     final void performSurfacePlacement() {
+        if (mDeferDepth > 0) {
+            return;
+        }
         int loopCount = 6;
         do {
             mTraversalScheduled = false;
@@ -340,6 +362,10 @@
                 // Don't remove this window until rotation has completed.
                 continue;
             }
+            // Discard the saved surface if window size is changed, it can't be reused.
+            if (win.mAppToken != null) {
+                win.mAppToken.destroySavedSurfaces();
+            }
             win.reportResized();
             mService.mResizingWindows.remove(i);
         }
@@ -371,7 +397,9 @@
                 if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
                     wallpaperDestroyed = true;
                 }
-                win.mWinAnimator.destroySurfaceLocked();
+                if (!win.shouldSaveSurface()) {
+                    win.mWinAnimator.destroySurfaceLocked();
+                }
             } while (i > 0);
             mService.mDestroySurface.clear();
         }
@@ -1163,7 +1191,10 @@
             if (animLp != null) {
                 int layer = -1;
                 for (int j = 0; j < wtoken.windows.size(); j++) {
-                    WindowState win = wtoken.windows.get(j);
+                    final WindowState win = wtoken.windows.get(j);
+                    // Clearing the mExiting flag before entering animation. It will be set
+                    // to true if app window is removed, or window relayout to invisible.
+                    win.mExiting = false;
                     if (win.mWinAnimator.mAnimLayer > layer) {
                         layer = win.mWinAnimator.mAnimLayer;
                     }
@@ -1174,6 +1205,8 @@
                 }
             }
             createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+
+            wtoken.restoreSavedSurfaces();
         }
 
         AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
@@ -1219,6 +1252,10 @@
                         + wtoken.allDrawn + " startingDisplayed="
                         + wtoken.startingDisplayed + " startingMoved="
                         + wtoken.startingMoved);
+
+                if (wtoken.mHasSavedSurface || wtoken.mAnimatingWithSavedSurface) {
+                    continue;
+                }
                 if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
                     return false;
                 }
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index 89b2a47..01acdef 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -414,12 +414,9 @@
         return NO_ERROR;
     }
     if (Surface::isValid(connection.mSurface)) {
-        connection.mSurface.clear();
-    }
-    if (connection.mSurface != NULL) {
         connection.mSurface->setSidebandStream(NULL);
-        connection.mSurface.clear();
     }
+    connection.mSurface.clear();
     if (connection.mThread != NULL) {
         connection.mThread->shutdown();
         connection.mThread.clear();
@@ -616,6 +613,9 @@
         return BAD_VALUE;
     }
     sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
+    if (!Surface::isValid(surface)) {
+        return BAD_VALUE;
+    }
     return tvInputHal->addOrUpdateStream(deviceId, streamId, surface);
 }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8385685..b4c8f96 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicepolicy;
 
+import com.google.android.collect.Sets;
+
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
@@ -88,6 +90,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.StorageManager;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
@@ -183,8 +186,6 @@
     private static final int MONITORING_CERT_NOTIFICATION_ID = R.string.ssl_ca_cert_warning;
     private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
 
-    private static final boolean DBG = false;
-
     private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
     private static final String ATTR_SETUP_COMPLETE = "setup-complete";
     private static final String ATTR_PERMISSION_POLICY = "permission-policy";
@@ -274,6 +275,7 @@
     final Injector mInjector;
     final IPackageManager mIPackageManager;
     final UserManager mUserManager;
+    final UserManagerInternal mUserManagerInternal;
 
     final LocalService mLocalService;
 
@@ -357,8 +359,10 @@
                     getSendingUserId());
             if (Intent.ACTION_BOOT_COMPLETED.equals(action)
                     || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) {
-                if (DBG) Slog.v(LOG_TAG, "Sending password expiration notifications for action "
-                        + action + " for user " + userHandle);
+                if (VERBOSE_LOG) {
+                    Slog.v(LOG_TAG, "Sending password expiration notifications for action "
+                            + action + " for user " + userHandle);
+                }
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
@@ -1014,7 +1018,7 @@
 
     private void handlePackagesChanged(String packageName, int userHandle) {
         boolean removed = false;
-        if (DBG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
+        if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
         DevicePolicyData policy = getUserData(userHandle);
         synchronized (this) {
             for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
@@ -1079,6 +1083,10 @@
             return UserManager.get(mContext);
         }
 
+        UserManagerInternal getUserManagerInternal() {
+            return LocalServices.getService(UserManagerInternal.class);
+        }
+
         NotificationManager getNotificationManager() {
             return mContext.getSystemService(NotificationManager.class);
         }
@@ -1233,6 +1241,7 @@
         mOwners = Preconditions.checkNotNull(injector.newOwners());
 
         mUserManager = Preconditions.checkNotNull(injector.getUserManager());
+        mUserManagerInternal = Preconditions.checkNotNull(injector.getUserManagerInternal());
         mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
 
         mLocalService = new LocalService();
@@ -1327,10 +1336,13 @@
         synchronized (this) {
             mOwners.load();
             findOwnerComponentIfNecessaryLocked();
+            migrateUserRestrictionsIfNecessaryLocked();
 
             // TODO PO may not have a class name either due to b/17652534.  Address that too.
 
             updateDeviceOwnerLocked();
+
+            // TODO Notify UM to update restrictions (?)
         }
     }
 
@@ -1350,14 +1362,113 @@
         if (doComponent == null) {
             Slog.e(LOG_TAG, "Device-owner isn't registered as device-admin");
         } else {
-            mOwners.setDeviceOwner(
+            mOwners.setDeviceOwnerWithRestrictionsMigrated(
                     doComponent,
                     mOwners.getDeviceOwnerName(),
-                    mOwners.getDeviceOwnerUserId());
+                    mOwners.getDeviceOwnerUserId(),
+                    !mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
             mOwners.writeDeviceOwner();
+            if (VERBOSE_LOG) {
+                Log.v(LOG_TAG, "Device owner component filled in");
+            }
         }
     }
 
+    /**
+     * We didn't use to persist user restrictions for each owners but only persisted in user
+     * manager.
+     */
+    private void migrateUserRestrictionsIfNecessaryLocked() {
+        boolean migrated = false;
+        // Migrate for the DO.  Basically all restrictions should be considered to be set by DO,
+        // except for the "system controlled" ones.
+        if (mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()) {
+            if (VERBOSE_LOG) {
+                Log.v(LOG_TAG, "Migrating DO user restrictions");
+            }
+            migrated = true;
+
+            // Migrate user 0 restrictions to DO, except for "system" restrictions.
+            final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
+
+            migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
+                    /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+
+            mOwners.setDeviceOwnerUserRestrictionsMigrated();
+        }
+
+        // Migrate for POs.  We have a few more exceptions.
+        final Set<String> normalExceptionList = Sets.newArraySet(
+                UserManager.DISALLOW_OUTGOING_CALLS,
+                UserManager.DISALLOW_SMS);
+        normalExceptionList.addAll(UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+
+        final Set<String> managedExceptionList = new ArraySet<>(normalExceptionList.size() + 1);
+        managedExceptionList.addAll(normalExceptionList);
+        managedExceptionList.add(UserManager.DISALLOW_WALLPAPER);
+
+        for (UserInfo ui : mUserManager.getUsers()) {
+            final int userId = ui.id;
+            if (mOwners.getProfileOwnerUserRestrictionsNeedsMigration(userId)) {
+                if (userId != UserHandle.USER_SYSTEM) {
+                    if (VERBOSE_LOG) {
+                        Log.v(LOG_TAG, "Migrating PO user restrictions for user " + userId);
+                    }
+                    migrated = true;
+
+                    final ActiveAdmin profileOwnerAdmin = getProfileOwnerAdminLocked(userId);
+
+                    final Set<String> exceptionList =
+                            ui.isManagedProfile() ? managedExceptionList : normalExceptionList;
+
+                    migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin,
+                            exceptionList);
+                }
+
+                mOwners.setProfileOwnerUserRestrictionsMigrated(userId);
+            }
+        }
+        if (VERBOSE_LOG && migrated) {
+            Log.v(LOG_TAG, "User restrictions migrated.");
+        }
+    }
+
+    private void migrateUserRestrictionsForUser(UserHandle user, ActiveAdmin admin,
+            Set<String> exceptionList) {
+        final Bundle origRestrictions = mUserManagerInternal.getBaseUserRestrictions(
+                user.getIdentifier());
+
+        final Bundle newSystemRestrictions = new Bundle();
+        final Bundle newOwnerRestrictions = new Bundle();
+
+        for (String key : origRestrictions.keySet()) {
+            if (!origRestrictions.getBoolean(key)) {
+                continue;
+            }
+            if (exceptionList.contains(key)) {
+                newSystemRestrictions.putBoolean(key, true);
+            } else {
+                newOwnerRestrictions.putBoolean(key, true);
+            }
+        }
+
+        if (VERBOSE_LOG) {
+            Log.v(LOG_TAG, "origRestrictions=" + origRestrictions);
+            Log.v(LOG_TAG, "newSystemRestrictions=" + newSystemRestrictions);
+            Log.v(LOG_TAG, "newOwnerRestrictions=" + newOwnerRestrictions);
+        }
+        mUserManagerInternal.setBaseUserRestrictionsByDpmsForMigration(user.getIdentifier(),
+                newSystemRestrictions);
+
+        if (admin != null) {
+            admin.ensureUserRestrictions().clear();
+            admin.ensureUserRestrictions().putAll(newOwnerRestrictions);
+        } else {
+            Slog.w(LOG_TAG, "ActiveAdmin for DO/PO not found. user=" + user.getIdentifier());
+        }
+        saveSettingsLocked(user.getIdentifier());
+    }
+
     private ComponentName findAdminComponentWithPackageLocked(String packageName, int userId) {
         final DevicePolicyData policy = getUserData(userId);
         final int n = policy.mAdminList.size();
@@ -1373,7 +1484,7 @@
                 nFound++;
             }
         }
-        if (nFound > 0) {
+        if (nFound > 1) {
             Slog.w(LOG_TAG, "Multiple DA found; assume the first one is DO.");
         }
         return found;
@@ -1636,6 +1747,9 @@
                 ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
                 : new File(mInjector.environmentGetUserSystemDirectory(userHandle),
                         DEVICE_POLICIES_XML).getAbsolutePath();
+        if (VERBOSE_LOG) {
+            Log.v(LOG_TAG, "Opening " + base);
+        }
         return new JournaledFile(new File(base), new File(base + ".tmp"));
     }
 
@@ -1808,7 +1922,8 @@
                     try {
                         DeviceAdminInfo dai = findAdmin(
                                 ComponentName.unflattenFromString(name), userHandle);
-                        if (DBG && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
+                        if (VERBOSE_LOG
+                                && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
                                 != userHandle)) {
                             Slog.w(LOG_TAG, "findAdmin returned an incorrect uid "
                                     + dai.getActivityInfo().applicationInfo.uid + " for user "
@@ -1988,8 +2103,10 @@
             long token = mInjector.binderClearCallingIdentity();
             try {
                 String value = cameraDisabled ? "1" : "0";
-                if (DBG) Slog.v(LOG_TAG, "Change in camera state ["
-                        + cameraPropertyForUser + "] = " + value);
+                if (VERBOSE_LOG) {
+                    Slog.v(LOG_TAG, "Change in camera state ["
+                            + cameraPropertyForUser + "] = " + value);
+                }
                 mInjector.systemPropertiesSet(cameraPropertyForUser, value);
             } finally {
                 mInjector.binderRestoreCallingIdentity(token);
@@ -4513,8 +4630,10 @@
         }
         UserHandle callingUser = mInjector.binderGetCallingUserHandle();
         // Check if this is the profile owner who is calling
-        getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        final ActiveAdmin admin =
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         synchronized (this) {
+            admin.userRestrictions = null;
             clearUserPoliciesLocked(callingUser);
             final int userId = callingUser.getIdentifier();
             mOwners.removeProfileOwner(userId);
@@ -4533,38 +4652,19 @@
 
         final long ident = mInjector.binderClearCallingIdentity();
         try {
-            clearUserRestrictions(userHandle);
             mIPackageManager.updatePermissionFlagsForAllApps(
                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                     0  /* flagValues */, userHandle.getIdentifier());
+            // TODO This will not revert audio mute restrictions if they were set.  b/24981972
+            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+                mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle.getIdentifier());
+            }
         } catch (RemoteException re) {
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
     }
 
-
-    private void clearUserRestrictions(UserHandle userHandle) {
-        Bundle userRestrictions = mUserManager.getUserRestrictions();
-        mUserManager.setUserRestrictions(new Bundle(), userHandle);
-        if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) {
-            try {
-                mInjector.getIAudioService().setMasterMute(true, 0, mContext.getPackageName(),
-                        userHandle.getIdentifier());
-            } catch (RemoteException e) {
-                // Not much we can do here.
-            }
-        }
-        if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) {
-            try {
-                mInjector.getIAudioService().setMicrophoneMute(true, mContext.getPackageName(),
-                        userHandle.getIdentifier());
-            } catch (RemoteException e) {
-                // Not much we can do here.
-            }
-        }
-    }
-
     @Override
     public boolean hasUserSetupCompleted() {
         return hasUserSetupCompleted(UserHandle.getCallingUserId());
@@ -5503,95 +5603,123 @@
     }
 
     @Override
-    public void setUserRestriction(ComponentName who, String key, boolean enabled) {
+    public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
         final UserHandle user = new UserHandle(userHandle);
-        synchronized (this) {
-            ActiveAdmin activeAdmin =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            boolean isDeviceOwner = isDeviceOwner(who);
-            if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
-                    && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
-                throw new SecurityException("Profile owners cannot set user restriction " + key);
-            }
-            if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
-                throw new SecurityException("User restriction " + key + " cannot be changed");
-            }
-            boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
+        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+            synchronized (this) {
+                ActiveAdmin activeAdmin =
+                        getActiveAdminForCallerLocked(who,
+                                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+                boolean isDeviceOwner = isDeviceOwner(who);
+                if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
+                        && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
+                    throw new SecurityException(
+                            "Profile owners cannot set user restriction " + key);
+                }
+                if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
+                    throw new SecurityException("User restriction " + key + " cannot be changed");
+                }
 
-            long id = mInjector.binderClearCallingIdentity();
-            try {
-                if (enabled && !alreadyRestricted) {
-                    if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
-                    } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
-                    } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
-                        mInjector.settingsSecurePutIntForUser(
-                                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
-                                userHandle);
-                    } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                        mInjector.settingsSecurePutIntForUser(
-                                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF,
-                                userHandle);
-                        mInjector.settingsSecurePutStringForUser(
-                                Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
-                                userHandle);
-                    } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
-                        // Only disable adb if changing for system user, since it is global
-                        // TODO: should this be admin user?
-                        if (userHandle == UserHandle.USER_SYSTEM) {
+                final long id = mInjector.binderClearCallingIdentity();
+                try {
+                    // Original value.
+                    final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
+
+                    // Save the restriction to ActiveAdmin.
+                    // TODO When DO sets a restriction, it'll always be treated as device-wide.
+                    // If there'll be a policy that can be set by both, we'll need scoping support,
+                    // and need to have another Bundle in DO active admin to hold restrictions as
+                    // PO.
+                    activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+                    saveSettingsLocked(userHandle);
+
+                    // Tell UserManager the new value.  Note this needs to be done before calling
+                    // into AudioService, because AS will check AppOps that'll be updated by UM.
+                    if (isDeviceOwner) {
+                        mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL();
+                    } else {
+                        mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
+                    }
+
+                    // New value.
+                    final boolean enabled = mUserManager.hasUserRestriction(key, user);
+
+                    // TODO The rest of the code should move to UserManagerService.
+
+                    if (enabled && !alreadyRestricted) {
+                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
+                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
+                        } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
+                            mInjector.settingsSecurePutIntForUser(
+                                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
+                                    userHandle);
+                        } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
+                            mInjector.settingsSecurePutIntForUser(
+                                    Settings.Secure.LOCATION_MODE,
+                                    Settings.Secure.LOCATION_MODE_OFF,
+                                    userHandle);
+                            mInjector.settingsSecurePutStringForUser(
+                                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+                                    userHandle);
+                        } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
+                            // Only disable adb if changing for system user, since it is global
+                            // TODO: should this be admin user?
+                            if (userHandle == UserHandle.USER_SYSTEM) {
+                                mInjector.settingsGlobalPutStringForUser(
+                                        Settings.Global.ADB_ENABLED, "0", userHandle);
+                            }
+                        } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
                             mInjector.settingsGlobalPutStringForUser(
-                                    Settings.Global.ADB_ENABLED, "0", userHandle);
+                                    Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+                                    userHandle);
+                            mInjector.settingsGlobalPutStringForUser(
+                                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+                                    userHandle);
+                        } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
+                            mInjector.settingsSecurePutIntForUser(
+                                    Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+                                    userHandle);
                         }
-                    } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
-                        mInjector.settingsGlobalPutStringForUser(
-                                Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
-                                userHandle);
-                        mInjector.settingsGlobalPutStringForUser(
-                                Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
-                                userHandle);
-                    } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
-                        mInjector.settingsSecurePutIntForUser(
-                                Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
-                                userHandle);
                     }
-                }
-                mUserManager.setUserRestriction(key, enabled, user);
-                activeAdmin.ensureUserRestrictions().putBoolean(key, enabled);
-                saveSettingsLocked(userHandle);
 
-                if (enabled != alreadyRestricted) {
-                    if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                        // Send out notifications however as some clients may want to reread the
-                        // value which actually changed due to a restriction having been applied.
-                        final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
-                        long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
-                        mInjector.systemPropertiesSet(property, Long.toString(version));
+                    if (enabled != alreadyRestricted) {
+                        if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
+                            // Send out notifications however as some clients may want to reread the
+                            // value which actually changed due to a restriction having been
+                            // applied.
+                            final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
+                            long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
+                            mInjector.systemPropertiesSet(property, Long.toString(version));
 
-                        final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
-                        Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
-                        mContext.getContentResolver().notifyChange(url, null, true, userHandle);
+                            final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+                            Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
+                            mContext.getContentResolver().notifyChange(url, null, true, userHandle);
+                        }
                     }
-                }
-                if (!enabled && alreadyRestricted) {
-                    if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMicrophoneMute(false, mContext.getPackageName(), userHandle);
-                    } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                        mInjector.getIAudioService()
-                                .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
+                    if (!enabled && alreadyRestricted) {
+                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMicrophoneMute(false, mContext.getPackageName(),
+                                            userHandle);
+                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+                            mInjector.getIAudioService()
+                                    .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
+                        }
                     }
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
+                } finally {
+                    mInjector.binderRestoreCallingIdentity(id);
                 }
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(id);
+
+                sendChangedNotification(userHandle);
             }
-            sendChangedNotification(userHandle);
         }
     }
 
@@ -5650,7 +5778,7 @@
             long id = mInjector.binderClearCallingIdentity();
 
             try {
-                if (DBG) {
+                if (VERBOSE_LOG) {
                     Slog.v(LOG_TAG, "installing " + packageName + " for "
                             + userId);
                 }
@@ -5705,7 +5833,9 @@
                         0, // no flags
                         primaryUser.id);
 
-                if (DBG) Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
+                if (VERBOSE_LOG) {
+                    Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
+                }
                 int numberOfAppsInstalled = 0;
                 if (activitiesToEnable != null) {
                     for (ResolveInfo info : activitiesToEnable) {
@@ -6275,7 +6405,8 @@
         }
     }
 
-    private final class LocalService extends DevicePolicyManagerInternal {
+    @VisibleForTesting
+    final class LocalService extends DevicePolicyManagerInternal {
         private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners;
 
         @Override
@@ -6322,6 +6453,30 @@
             }
         }
 
+        @Override
+        public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) {
+            synchronized (DevicePolicyManagerService.this) {
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+
+                final Bundle deviceOwnerRestrictions =
+                        deviceOwner == null ? null : deviceOwner.userRestrictions;
+                final Bundle profileOwnerRestrictions =
+                        profileOwner == null ? null : profileOwner.userRestrictions;
+
+                if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null) {
+                    // No restrictions to merge.
+                    return inBundle;
+                }
+
+                final Bundle composed = new Bundle(inBundle);
+                UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
+                UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
+
+                return composed;
+            }
+        }
+
         private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
             final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
             synchronized (DevicePolicyManagerService.this) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 799267d..12b3775 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -30,7 +30,6 @@
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.Preconditions;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -75,6 +74,7 @@
     private static final String ATTR_PACKAGE = "package";
     private static final String ATTR_COMPONENT_NAME = "component";
     private static final String ATTR_USERID = "userId";
+    private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated";
 
     private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";
 
@@ -155,7 +155,16 @@
             Slog.e(TAG, "Invalid user id for device owner user: " + userId);
             return;
         }
-        mDeviceOwner = new OwnerInfo(ownerName, admin);
+        // For a newly set DO, there's no need for migration.
+        setDeviceOwnerWithRestrictionsMigrated(admin, ownerName, userId,
+                /* userRestrictionsMigrated =*/ true);
+    }
+
+    // Note this should be only called during migration.  Normally when DO is set,
+    // userRestrictionsMigrated should always be true.
+    void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId,
+            boolean userRestrictionsMigrated) {
+        mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated);
         mDeviceOwnerUserId = userId;
     }
 
@@ -165,7 +174,9 @@
     }
 
     void setProfileOwner(ComponentName admin, String ownerName, int userId) {
-        mProfileOwners.put(userId, new OwnerInfo(ownerName, admin));
+        // For a newly set PO, there's no need for migration.
+        mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
+                /* userRestrictionsMigrated =*/ true));
     }
 
     void removeProfileOwner(int userId) {
@@ -207,6 +218,38 @@
         return mDeviceOwner != null;
     }
 
+    /**
+     * @return true if user restrictions need to be migrated for DO.
+     */
+    boolean getDeviceOwnerUserRestrictionsNeedsMigration() {
+        return mDeviceOwner != null && !mDeviceOwner.userRestrictionsMigrated;
+    }
+
+    /**
+     * @return true if user restrictions need to be migrated for PO.
+     */
+    boolean getProfileOwnerUserRestrictionsNeedsMigration(int userId) {
+        OwnerInfo profileOwner = mProfileOwners.get(userId);
+        return profileOwner != null && !profileOwner.userRestrictionsMigrated;
+    }
+
+    /** Sets the user restrictions migrated flag, and also writes to the file. */
+    void setDeviceOwnerUserRestrictionsMigrated() {
+        if (mDeviceOwner != null) {
+            mDeviceOwner.userRestrictionsMigrated = true;
+        }
+        writeDeviceOwner();
+    }
+
+    /** Sets the user restrictions migrated flag, and also writes to the file.  */
+    void setProfileOwnerUserRestrictionsMigrated(int userId) {
+        OwnerInfo profileOwner = mProfileOwners.get(userId);
+        if (profileOwner != null) {
+            profileOwner.userRestrictionsMigrated = true;
+        }
+        writeProfileOwner(userId);
+    }
+
     private boolean readLegacyOwnerFile(File file) {
         if (!file.exists()) {
             // Already migrated or the device has no owners.
@@ -226,7 +269,8 @@
                 if (tag.equals(TAG_DEVICE_OWNER)) {
                     String name = parser.getAttributeValue(null, ATTR_NAME);
                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-                    mDeviceOwner = new OwnerInfo(name, packageName);
+                    mDeviceOwner = new OwnerInfo(name, packageName,
+                            /* userRestrictionsMigrated =*/ false);
                     mDeviceOwnerUserId = UserHandle.USER_SYSTEM;
                 } else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
                     // Deprecated tag
@@ -241,7 +285,8 @@
                         ComponentName admin = ComponentName.unflattenFromString(
                                 profileOwnerComponentStr);
                         if (admin != null) {
-                            profileOwnerInfo = new OwnerInfo(profileOwnerName, admin);
+                            profileOwnerInfo = new OwnerInfo(profileOwnerName, admin,
+                                /* userRestrictionsMigrated =*/ false);
                         } else {
                             // This shouldn't happen but switch from package name -> component name
                             // might have written bad device owner files. b/17652534
@@ -250,7 +295,8 @@
                         }
                     }
                     if (profileOwnerInfo == null) {
-                        profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName);
+                        profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName,
+                                /* userRestrictionsMigrated =*/ false);
                     }
                     mProfileOwners.put(userId, profileOwnerInfo);
                 } else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) {
@@ -503,21 +549,24 @@
         }
     }
 
-    private static class OwnerInfo {
+    static class OwnerInfo {
         public final String name;
         public final String packageName;
         public final ComponentName admin;
+        public boolean userRestrictionsMigrated;
 
-        public OwnerInfo(String name, String packageName) {
+        public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated) {
             this.name = name;
             this.packageName = packageName;
             this.admin = new ComponentName(packageName, "");
+            this.userRestrictionsMigrated = userRestrictionsMigrated;
         }
 
-        public OwnerInfo(String name, ComponentName admin) {
+        public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated) {
             this.name = name;
             this.admin = admin;
             this.packageName = admin.getPackageName();
+            this.userRestrictionsMigrated = userRestrictionsMigrated;
         }
 
         public void writeToXml(XmlSerializer out, String tag) throws IOException {
@@ -529,6 +578,8 @@
             if (admin != null) {
                 out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString());
             }
+            out.attribute(null, ATTR_USER_RESTRICTIONS_MIGRATED,
+                    String.valueOf(userRestrictionsMigrated));
             out.endTag(null, tag);
         }
 
@@ -537,12 +588,16 @@
             final String name = parser.getAttributeValue(null, ATTR_NAME);
             final String componentName =
                     parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
+            final String userRestrictionsMigratedStr =
+                    parser.getAttributeValue(null, ATTR_USER_RESTRICTIONS_MIGRATED);
+            final boolean userRestrictionsMigrated =
+                    ("true".equals(userRestrictionsMigratedStr));
 
             // Has component name?  If so, return [name, component]
             if (componentName != null) {
                 final ComponentName admin = ComponentName.unflattenFromString(componentName);
                 if (admin != null) {
-                    return new OwnerInfo(name, admin);
+                    return new OwnerInfo(name, admin, userRestrictionsMigrated);
                 } else {
                     // This shouldn't happen but switch from package name -> component name
                     // might have written bad device owner files. b/17652534
@@ -552,7 +607,7 @@
             }
 
             // Else, build with [name, package]
-            return new OwnerInfo(name, packageName);
+            return new OwnerInfo(name, packageName, userRestrictionsMigrated);
         }
 
         public void dump(String prefix, PrintWriter pw) {
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 28cb114..c9efc69 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -417,9 +417,10 @@
                 encap, mTransactionId, getSecs(), clientAddress,
                 DO_UNICAST, mHwAddr, requestedAddress,
                 serverAddress, REQUESTED_PARAMS, null);
+        String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
         String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
                              " request=" + requestedAddress.getHostAddress() +
-                             " to=" + serverAddress.getHostAddress();
+                             " serverid=" + serverStr;
         return transmitPacket(packet, description, to);
     }
 
@@ -822,7 +823,8 @@
         public void enter() {
             super.enter();
             if (!setIpAddress(mDhcpLease.ipAddress) ||
-                    !connectUdpSock((mDhcpLease.serverAddress))) {
+                    (mDhcpLease.serverAddress != null &&
+                            !connectUdpSock((mDhcpLease.serverAddress)))) {
                 notifyFailure();
                 // There's likely no point in going into DhcpInitState here, we'll probably just
                 // repeat the transaction, get the same IP address as before, and fail.
@@ -878,11 +880,15 @@
         }
 
         protected boolean sendPacket() {
+            // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
+            // http://b/25343517 . Try to make things work anyway by using broadcast renews.
+            Inet4Address to = (mDhcpLease.serverAddress != null) ?
+                    mDhcpLease.serverAddress : INADDR_BROADCAST;
             return sendRequestPacket(
                     (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
                     INADDR_ANY,                                        // DHCP_REQUESTED_IP
-                    INADDR_ANY,                                        // DHCP_SERVER_IDENTIFIER
-                    (Inet4Address) mDhcpLease.serverAddress);          // packet destination address
+                    null,                                              // DHCP_SERVER_IDENTIFIER
+                    to);                                               // packet destination address
         }
 
         protected void receivePacket(DhcpPacket packet) {
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 53f55cd..88155f7 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -403,7 +403,6 @@
 
     // TODO: simply the number of objects by making this extend Thread.
     private final class NetlinkSocketObserver implements Runnable {
-        private static final String TAG = "NetlinkSocketObserver";
         private NetlinkSocket mSocket;
 
         @Override
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml
new file mode 100644
index 0000000..9564969
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<device-owner package="com.android.frameworks.servicestests" />
+<profile-owner package="com.android.frameworks.servicestests" name="0" userId="10" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2" />
+<profile-owner package="com.android.frameworks.servicestests" name="0" userId="11" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3" />
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml
new file mode 100644
index 0000000..48cb814
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+    <admin name="com.google.android.gms/com.google.android.gms.mdm.receivers.MdmDeviceAdminReceiver">
+    </admin>
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml
new file mode 100644
index 0000000..6b53840
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2">
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml
new file mode 100644
index 0000000..2bcc5d4
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3">
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
new file mode 100644
index 0000000..dfa9f8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.devicepolicy;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Pair;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
+    private DpmMockContext mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mContext = getContext();
+
+        when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
+                .thenReturn(true);
+    }
+
+    public void testMigration() throws Exception {
+        final File user10dir = mMockContext.addUser(10, 0);
+        final File user11dir = mMockContext.addUser(11, UserInfo.FLAG_MANAGED_PROFILE);
+        final File user12dir = mMockContext.addUser(12, 0);
+
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        setUpPackageManagerForAdmin(admin2, UserHandle.getUid(10, 123));
+        setUpPackageManagerForAdmin(admin3, UserHandle.getUid(11, 456));
+
+        // Create the legacy owners & policies file.
+        DpmTestUtils.writeToFile(
+                (new File(mContext.dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml"));
+
+        DpmTestUtils.writeToFile(
+                (new File(mContext.systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml"));
+
+        DpmTestUtils.writeToFile(
+                (new File(user10dir, "device_policies.xml")).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml"));
+        DpmTestUtils.writeToFile(
+                (new File(user11dir, "device_policies.xml")).getAbsoluteFile(),
+                DpmTestUtils.readAsset(mRealTestContext,
+                        "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml"));
+
+        // Set up UserManager
+        when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+                eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_ADD_USER,
+                UserManager.DISALLOW_RECORD_AUDIO));
+
+        when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+                eq(10))).thenReturn(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_REMOVE_USER,
+                UserManager.DISALLOW_SMS,
+                UserManager.DISALLOW_OUTGOING_CALLS,
+                UserManager.DISALLOW_WALLPAPER,
+                UserManager.DISALLOW_RECORD_AUDIO));
+
+        when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+                eq(11))).thenReturn(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_REMOVE_USER,
+                UserManager.DISALLOW_SMS,
+                UserManager.DISALLOW_OUTGOING_CALLS,
+                UserManager.DISALLOW_WALLPAPER,
+                UserManager.DISALLOW_RECORD_AUDIO));
+
+        final Map<Integer, Bundle> newBaseRestrictions = new HashMap<>();
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Integer userId = (Integer) invocation.getArguments()[0];
+                Bundle bundle = (Bundle) invocation.getArguments()[1];
+
+                newBaseRestrictions.put(userId, bundle);
+
+                return null;
+            }
+        }).when(mContext.userManagerInternal).setBaseUserRestrictionsByDpmsForMigration(
+                anyInt(), any(Bundle.class));
+
+        // Initialize DPM/DPMS and let it migrate the persisted information.
+        // (Need clearCallingIdentity() to pass permission checks.)
+
+        final DevicePolicyManagerServiceTestable dpms;
+
+        final long ident = mContext.binder.clearCallingIdentity();
+        try {
+            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+            dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+
+            dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+            dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+        } finally {
+            mContext.binder.restoreCallingIdentity(ident);
+        }
+
+        // Now all information should be migrated.
+        assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
+        assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+        assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+        assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12));
+
+        // Check the new base restrictions.
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_RECORD_AUDIO
+                ),
+                newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_SMS,
+                        UserManager.DISALLOW_OUTGOING_CALLS,
+                        UserManager.DISALLOW_RECORD_AUDIO
+                ),
+                newBaseRestrictions.get(10));
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_SMS,
+                        UserManager.DISALLOW_OUTGOING_CALLS,
+                        UserManager.DISALLOW_WALLPAPER,
+                        UserManager.DISALLOW_RECORD_AUDIO
+                ),
+                newBaseRestrictions.get(11));
+
+        // Check the new owner restrictions.
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_ADD_USER
+                ),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions());
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_REMOVE_USER,
+                        UserManager.DISALLOW_WALLPAPER
+                ),
+                dpms.getProfileOwnerAdminLocked(10).ensureUserRestrictions());
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_REMOVE_USER
+                ),
+                dpms.getProfileOwnerAdminLocked(11).ensureUserRestrictions());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index b109e7b..2c01b8a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -27,6 +27,7 @@
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.view.IWindowManager;
 
 import java.io.File;
@@ -107,6 +108,11 @@
         }
 
         @Override
+        UserManagerInternal getUserManagerInternal() {
+            return context.userManagerInternal;
+        }
+
+        @Override
         PowerManagerInternal getPowerManagerInternal() {
             return context.powerManagerInternal;
         }
@@ -153,7 +159,7 @@
 
         @Override
         String getDevicePolicyFilePathForSystemUser() {
-            return context.systemUserDataDir.getAbsolutePath();
+            return context.systemUserDataDir.getAbsolutePath() + "/";
         }
 
         @Override
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 d6a60c7..727858b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -27,7 +27,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
@@ -70,9 +69,6 @@
     private DpmMockContext mContext;
     public DevicePolicyManager dpm;
     public DevicePolicyManagerServiceTestable dpms;
-    public ComponentName admin1;
-    public ComponentName admin2;
-    public ComponentName admin3;
 
     @Override
     protected void setUp() throws Exception {
@@ -85,10 +81,6 @@
 
         initializeDpms();
 
-        admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
-        admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
-        admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
-
         setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
         setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
         setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
@@ -113,67 +105,6 @@
         }
     }
 
-    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) throws Exception {
-        setUpPackageManagerForAdmin(admin, packageUid,
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
-    }
-
-    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
-            int enabledSetting) throws Exception {
-
-        // Set up queryBroadcastReceivers().
-
-        final Intent resolveIntent = new Intent();
-        resolveIntent.setComponent(admin);
-        final List<ResolveInfo> realResolveInfo =
-                mRealTestContext.getPackageManager().queryBroadcastReceivers(
-                        resolveIntent,
-                        PackageManager.GET_META_DATA);
-        assertNotNull(realResolveInfo);
-        assertEquals(1, realResolveInfo.size());
-
-        // We need to change AI, so set a clone.
-        realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
-
-        // We need to rewrite the UID in the activity info.
-        realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
-
-        doReturn(realResolveInfo).when(mContext.packageManager).queryBroadcastReceivers(
-                MockUtils.checkIntentComponent(admin),
-                eq(PackageManager.GET_META_DATA
-                        | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
-                eq(UserHandle.getUserId(packageUid)));
-
-        // Set up getApplicationInfo().
-
-        final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
-                mRealTestContext.getPackageManager().getApplicationInfo(
-                        admin1.getPackageName(),
-                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
-
-        ai.enabledSetting = enabledSetting;
-        ai.uid = packageUid;
-
-        doReturn(ai).when(mContext.ipackageManager).getApplicationInfo(
-                eq(admin1.getPackageName()),
-                eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
-                eq(UserHandle.getUserId(packageUid)));
-
-        // Set up getPackageInfo().
-
-        final PackageInfo pi = DpmTestUtils.cloneParcelable(
-                mRealTestContext.getPackageManager().getPackageInfo(
-                        admin1.getPackageName(), 0));
-        assertTrue(pi.applicationInfo.flags != 0);
-
-        pi.applicationInfo.uid = packageUid;
-
-        doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
-                eq(admin1.getPackageName()),
-                eq(0),
-                eq(UserHandle.getUserId(packageUid)));
-    }
-
     private void setUpUserManager() {
         // Emulate UserManager.set/getApplicationRestriction().
         final Map<Pair<String, UserHandle>, Bundle> appRestrictions = new HashMap<>();
@@ -220,7 +151,7 @@
         assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
 
         // Check
-        assertEquals(admin1, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
+        assertEquals(admin, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
     }
 
     public void testHasNoFeature() throws Exception {
@@ -743,6 +674,8 @@
         assertEquals("", dpms.getDeviceOwner().getClassName());
 
         // Then create a new DPMS to have it load the settings from files.
+        when(mContext.userManager.getUserRestrictions(any(UserHandle.class)))
+                .thenReturn(new Bundle());
         initializeDpms();
 
         // Now the DO component name is a full name.
@@ -802,32 +735,33 @@
         assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
                 UserHandle.USER_SYSTEM));
 
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
 
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_SMS));
-        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+        );
 
         // TODO Check inner calls.
         // TODO Make sure restrictions are written to the file.
@@ -836,42 +770,106 @@
     public void testSetUserRestriction_asPo() {
         setAsProfileOwner(admin1);
 
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+                        UserManager.DISALLOW_OUTGOING_CALLS
+                ),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
 
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(
+                        UserManager.DISALLOW_OUTGOING_CALLS
+                ),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
 
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
-        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
-                .ensureUserRestrictions()
-                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .ensureUserRestrictions()
+        );
 
         // TODO Check inner calls.
         // TODO Make sure restrictions are written to the file.
     }
+
+    public void testGetComposedUserRestrictions_noDoNoPo() throws Exception {
+        final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS);
+
+        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
+                UserHandle.USER_SYSTEM, in);
+        assertTrue(in == actual);
+
+        actual = dpms.mLocalService.getComposedUserRestrictions(
+                DpmMockContext.CALLER_USER_HANDLE, in);
+        assertTrue(in == actual);
+    }
+
+    public void testGetComposedUserRestrictions() throws Exception {
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+        // First, set DO.
+
+        // Call from a process on the system user.
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+        // Make sure admin1 is installed on system user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+        // Call.
+        dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
+        assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
+                UserHandle.USER_SYSTEM));
+
+        dpm.addUserRestriction(admin1, "rest1");
+        dpm.addUserRestriction(admin1, "rest2");
+
+        // Set PO on CALLER_USER_HANDLE.
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+        setAsProfileOwner(admin2);
+
+        dpm.addUserRestriction(admin2, "restA");
+        dpm.addUserRestriction(admin2, "restB");
+
+        final Bundle in = DpmTestUtils.newRestrictions("abc");
+
+        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
+                UserHandle.USER_SYSTEM, in);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
+                actual);
+
+        actual = dpms.mLocalService.getComposedUserRestrictions(
+                DpmMockContext.CALLER_USER_HANDLE, in);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"),
+                actual);
+
+        actual = dpms.mLocalService.getComposedUserRestrictions(
+                DpmMockContext.CALLER_USER_HANDLE + 1, in);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
+                actual);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index d1b4835..cc337b0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -36,6 +36,7 @@
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 import android.view.IWindowManager;
@@ -203,6 +204,7 @@
     public final EnvironmentForMock environment;
     public final SystemPropertiesForMock systemProperties;
     public final UserManager userManager;
+    public final UserManagerInternal userManagerInternal;
     public final UserManagerForMock userManagerForMock;
     public final PowerManagerForMock powerManager;
     public final PowerManagerInternal powerManagerInternal;
@@ -233,6 +235,7 @@
         environment = mock(EnvironmentForMock.class);
         systemProperties= mock(SystemPropertiesForMock.class);
         userManager = mock(UserManager.class);
+        userManagerInternal = mock(UserManagerInternal.class);
         userManagerForMock = mock(UserManagerForMock.class);
         powerManager = mock(PowerManagerForMock.class);
         powerManagerInternal = mock(PowerManagerInternal.class);
@@ -257,6 +260,9 @@
 
         // System user is always running.
         setUserRunning(UserHandle.USER_SYSTEM, true);
+
+        // This method must return an object.
+        when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object());
     }
 
     public File addUser(int userId, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 63bf125..e11f3fb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -16,10 +16,21 @@
 
 package com.android.server.devicepolicy;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
 import android.test.AndroidTestCase;
 
 import java.io.File;
+import java.util.List;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 
 public abstract class DpmTestBase extends AndroidTestCase {
     public static final String TAG = "DpmTest";
@@ -29,6 +40,10 @@
 
     public File dataDir;
 
+    public ComponentName admin1;
+    public ComponentName admin2;
+    public ComponentName admin3;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -37,10 +52,77 @@
 
         mMockContext = new DpmMockContext(
                 mRealTestContext, new File(mRealTestContext.getCacheDir(), "test-data"));
+
+        admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
+        admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
+        admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
     }
 
     @Override
     public DpmMockContext getContext() {
         return mMockContext;
     }
+
+
+    protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid)
+            throws Exception {
+        setUpPackageManagerForAdmin(admin, packageUid,
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+    }
+
+    protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
+            int enabledSetting) throws Exception {
+
+        // Set up queryBroadcastReceivers().
+
+        final Intent resolveIntent = new Intent();
+        resolveIntent.setComponent(admin);
+        final List<ResolveInfo> realResolveInfo =
+                mRealTestContext.getPackageManager().queryBroadcastReceivers(
+                        resolveIntent,
+                        PackageManager.GET_META_DATA);
+        assertNotNull(realResolveInfo);
+        assertEquals(1, realResolveInfo.size());
+
+        // We need to change AI, so set a clone.
+        realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
+
+        // We need to rewrite the UID in the activity info.
+        realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
+
+        doReturn(realResolveInfo).when(mMockContext.packageManager).queryBroadcastReceivers(
+                MockUtils.checkIntentComponent(admin),
+                eq(PackageManager.GET_META_DATA
+                        | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
+                eq(UserHandle.getUserId(packageUid)));
+
+        // Set up getApplicationInfo().
+
+        final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
+                mRealTestContext.getPackageManager().getApplicationInfo(
+                        admin.getPackageName(),
+                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
+
+        ai.enabledSetting = enabledSetting;
+        ai.uid = packageUid;
+
+        doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
+                eq(admin.getPackageName()),
+                eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
+                eq(UserHandle.getUserId(packageUid)));
+
+        // Set up getPackageInfo().
+
+        final PackageInfo pi = DpmTestUtils.cloneParcelable(
+                mRealTestContext.getPackageManager().getPackageInfo(
+                        admin.getPackageName(), 0));
+        assertTrue(pi.applicationInfo.flags != 0);
+
+        pi.applicationInfo.uid = packageUid;
+
+        doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo(
+                eq(admin.getPackageName()),
+                eq(0),
+                eq(UserHandle.getUserId(packageUid)));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
index 7506273..cceb2d2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
@@ -16,21 +16,35 @@
 
 package com.android.server.devicepolicy;
 
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import android.content.Context;
+import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.Printer;
 
 import org.junit.Assert;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
-public class DpmTestUtils {
-    private DpmTestUtils() {
-    }
+import junit.framework.AssertionFailedError;
 
+public class DpmTestUtils extends AndroidTestCase {
     public static void clearDir(File dir) {
         if (dir.exists()) {
             Assert.assertTrue("failed to delete dir", FileUtils.deleteContents(dir));
@@ -43,6 +57,44 @@
         return list == null ? 0 : list.size();
     }
 
+    public static Bundle newRestrictions(String... restrictions) {
+        final Bundle ret = new Bundle();
+        for (String restriction : restrictions) {
+            ret.putBoolean(restriction, true);
+        }
+        return ret;
+    }
+
+    public static void assertRestrictions(Bundle expected, Bundle actual) {
+        final ArrayList<String> elist;
+        if (expected == null) {
+            elist = null;
+        } else {
+            elist = Lists.newArrayList();
+            for (String key : expected.keySet()) {
+                if (expected.getBoolean(key)) {
+                    elist.add(key);
+                }
+            }
+            Collections.sort(elist);
+        }
+
+        final ArrayList<String> alist;
+        if (actual == null) {
+            alist = null;
+        } else {
+            alist = Lists.newArrayList();
+            for (String key : actual.keySet()) {
+                if (actual.getBoolean(key)) {
+                    alist.add(key);
+                }
+            }
+            Collections.sort(alist);
+        }
+
+        assertEquals(elist, alist);
+    }
+
     public static <T extends Parcelable> T cloneParcelable(T source) {
         Parcel p = Parcel.obtain();
         p.writeParcelable(source, 0);
@@ -58,4 +110,57 @@
             Log.i(DpmTestBase.TAG, x);
         }
     };
+
+    public static String readAsset(Context context, String assetPath) throws IOException {
+        final StringBuilder sb = new StringBuilder();
+        try (BufferedReader br = new BufferedReader(
+                new InputStreamReader(
+                        context.getResources().getAssets().open(assetPath)))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+                sb.append(System.lineSeparator());
+            }
+        }
+        return sb.toString();
+    }
+
+    public static void writeToFile(File path, String content)
+            throws IOException {
+        path.getParentFile().mkdirs();
+
+        try (FileWriter writer = new FileWriter(path)) {
+            Log.i(DpmTestBase.TAG, "Writing to " + path);
+            Log.i(DpmTestBase.TAG, content);
+            writer.write(content);
+        }
+    }
+
+    private static boolean checkAssertRestrictions(Bundle a, Bundle b) {
+        try {
+            assertRestrictions(a, b);
+            return true;
+        } catch (AssertionFailedError e) {
+            return false;
+        }
+    }
+
+    public void testAssertRestrictions() {
+        final Bundle a = newRestrictions();
+        final Bundle b = newRestrictions("a");
+        final Bundle c = newRestrictions("a");
+        final Bundle d = newRestrictions("b", "c");
+        final Bundle e = newRestrictions("b", "c");
+
+        assertTrue(checkAssertRestrictions(null, null));
+        assertFalse(checkAssertRestrictions(null, a));
+        assertFalse(checkAssertRestrictions(a, null));
+        assertTrue(checkAssertRestrictions(a, a));
+
+        assertFalse(checkAssertRestrictions(a, b));
+        assertTrue(checkAssertRestrictions(b, c));
+
+        assertFalse(checkAssertRestrictions(c, d));
+        assertTrue(checkAssertRestrictions(d, e));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 79845d2..4e11762 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -47,31 +47,6 @@
  (mmma frameworks/base/services/tests/servicestests/ for non-ninja build)
  */
 public class OwnersTest extends DpmTestBase {
-    private String readAsset(String assetPath) throws IOException {
-        final StringBuilder sb = new StringBuilder();
-        try (BufferedReader br = new BufferedReader(
-                new InputStreamReader(
-                        mRealTestContext.getResources().getAssets().open(assetPath)))) {
-            String line;
-            while ((line = br.readLine()) != null) {
-                sb.append(line);
-                sb.append(System.lineSeparator());
-            }
-        }
-        return sb.toString();
-    }
-
-    private void createLegacyFile(File path, String content)
-            throws IOException {
-        path.getParentFile().mkdirs();
-
-        try (FileWriter writer = new FileWriter(path)) {
-            Log.i(TAG, "Writing to " + path);
-            Log.i(TAG, content);
-            writer.write(content);
-        }
-    }
-
     public void testUpgrade01() throws Exception {
         getContext().addUsers(10, 11, 20, 21);
 
@@ -79,8 +54,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test01/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml"));
 
             owners.load();
 
@@ -99,6 +74,12 @@
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -110,6 +91,12 @@
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -120,8 +107,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test02/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml"));
 
             owners.load();
 
@@ -142,6 +129,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -156,6 +149,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -166,8 +165,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test03/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml"));
 
             owners.load();
 
@@ -196,6 +195,12 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -218,9 +223,19 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
+    /**
+     * Note this also tests {@link Owners#setDeviceOwnerUserRestrictionsMigrated()}
+     * and {@link  Owners#setProfileOwnerUserRestrictionsMigrated(int)}.
+     */
     public void testUpgrade04() throws Exception {
         getContext().addUsers(10, 11, 20, 21);
 
@@ -228,8 +243,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test04/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
 
             owners.load();
 
@@ -262,6 +277,12 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -288,6 +309,40 @@
                     owners.getProfileOwnerComponent(11));
             assertEquals("1", owners.getProfileOwnerName(11));
             assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+            assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+            owners.setDeviceOwnerUserRestrictionsMigrated();
+        }
+
+        {
+            final OwnersTestable owners = new OwnersTestable(getContext());
+            owners.load();
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+            owners.setProfileOwnerUserRestrictionsMigrated(11);
+        }
+
+        {
+            final OwnersTestable owners = new OwnersTestable(getContext());
+            owners.load();
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+            owners.setProfileOwnerUserRestrictionsMigrated(11);
         }
     }
 
@@ -298,8 +353,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test05/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml"));
 
             owners.load();
 
@@ -319,6 +374,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -332,6 +393,12 @@
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -342,8 +409,8 @@
         {
             final OwnersTestable owners = new OwnersTestable(getContext());
 
-            createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                    readAsset("OwnersTest/test06/input.xml"));
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                    DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml"));
 
             owners.load();
 
@@ -362,6 +429,12 @@
 
             assertNotNull(owners.getSystemUpdatePolicy());
             assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
 
         // Then re-read and check.
@@ -375,6 +448,12 @@
 
             assertNotNull(owners.getSystemUpdatePolicy());
             assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
+
+            assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+            assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
         }
     }
 
@@ -384,8 +463,8 @@
         final OwnersTestable owners = new OwnersTestable(getContext());
 
         // First, migrate to create new-style config files.
-        createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
-                readAsset("OwnersTest/test04/input.xml"));
+        DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+                DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
 
         owners.load();
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 86f5ed7..66c7dbb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -211,11 +211,14 @@
 
     public void testRestrictions() {
         UserInfo testUser = createUser("User 1", 0);
-        Bundle restrictions = new Bundle();
-        restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true);
-        restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, false);
-        mUserManager.setUserRestrictions(restrictions, new UserHandle(testUser.id));
+
+        mUserManager.setUserRestriction(
+                UserManager.DISALLOW_INSTALL_APPS, true, new UserHandle(testUser.id));
+        mUserManager.setUserRestriction(
+                UserManager.DISALLOW_CONFIG_WIFI, false, new UserHandle(testUser.id));
+
         Bundle stored = mUserManager.getUserRestrictions(new UserHandle(testUser.id));
+        // Note this will fail if DO already sets those restrictions.
         assertEquals(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI), false);
         assertEquals(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS), false);
         assertEquals(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS), true);
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 31763e7..129e5377 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -212,6 +212,10 @@
     }
 
     private AlsaDevice waitForAlsaDevice(int card, int device, int type) {
+        if (DEBUG) {
+            Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")");
+        }
+
         AlsaDevice testDevice = new AlsaDevice(type, card, device);
 
         // This value was empirically determined.
@@ -292,7 +296,8 @@
      */
     /* package */ UsbAudioDevice selectAudioCard(int card) {
         if (DEBUG) {
-            Slog.d(TAG, "selectAudioCard() card:" + card);
+            Slog.d(TAG, "selectAudioCard() card:" + card
+                    + " isCardUsb(): " + mCardsParser.isCardUsb(card));
         }
         if (!mCardsParser.isCardUsb(card)) {
             // Don't. AudioPolicyManager has logic for falling back to internal devices.
@@ -304,6 +309,10 @@
 
         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
         boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
+        if (DEBUG) {
+            Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
+        }
+
         int deviceClass =
             (mCardsParser.isCardUsb(card)
                 ? UsbAudioDevice.kAudioDeviceClass_External
@@ -320,10 +329,6 @@
             return null;
         }
 
-        if (DEBUG) {
-            Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
-        }
-
         UsbAudioDevice audioDevice =
                 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
         AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
@@ -339,14 +344,13 @@
         if (DEBUG) {
             Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
         }
-        mCardsParser.scan();
         return selectAudioCard(mCardsParser.getDefaultCard());
     }
 
     /* package */ void usbDeviceAdded(UsbDevice usbDevice) {
        if (DEBUG) {
           Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() +
-                  "nm:" + usbDevice.getProductName());
+                  " nm:" + usbDevice.getProductName());
         }
 
         // Is there an audio interface in there?
@@ -361,27 +365,22 @@
                 isAudioDevice = true;
             }
         }
+
+        if (DEBUG) {
+            Slog.d(TAG, "  isAudioDevice: " + isAudioDevice);
+        }
         if (!isAudioDevice) {
             return;
         }
 
-        ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords();
-        mCardsParser.scan();
-
-        int addedCard = -1;
-        ArrayList<AlsaCardsParser.AlsaCardRecord>
-            newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs);
-        if (newScanRecs.size() > 0) {
-            // This is where we select the just connected device
-            // NOTE - to switch to prefering the first-connected device, just always
-            // take the else clause below.
-            addedCard = newScanRecs.get(0).mCardNum;
-        } else {
-            addedCard = mCardsParser.getDefaultUsbCard();
-        }
+        int addedCard = mCardsParser.getDefaultUsbCard();
 
         // If the default isn't a USB device, let the existing "select internal mechanism"
         // handle the selection.
+        if (DEBUG) {
+            Slog.d(TAG, "  mCardsParser.isCardUsb(" + addedCard + ") = "
+                        + mCardsParser.isCardUsb(addedCard));
+        }
         if (mCardsParser.isCardUsb(addedCard)) {
             UsbAudioDevice audioDevice = selectAudioCard(addedCard);
             if (audioDevice != null) {
@@ -429,6 +428,10 @@
                 }
             }
         }
+
+        if (DEBUG) {
+            Slog.d(TAG, "deviceAdded() - done");
+        }
     }
 
     /* package */ void usbDeviceRemoved(UsbDevice usbDevice) {
@@ -439,7 +442,7 @@
 
         UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
         if (audioDevice != null) {
-            if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
+            if (audioDevice.mHasPlayback || audioDevice.mHasCapture) {
                 notifyDeviceState(audioDevice, false);
 
                 // if there any external devices left, select one of them
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 3160e39..e0f95cf 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -756,6 +756,8 @@
                         if (mUsbDataUnlocked && active && mCurrentUser != UserHandle.USER_NULL) {
                             Slog.v(TAG, "Current user switched to " + mCurrentUser
                                     + "; resetting USB host stack for MTP or PTP");
+                            // avoid leaking sensitive data from previous user
+                            mUsbDataUnlocked = false;
                             setEnabledFunctions(mCurrentFunctions, true);
                         }
                         mCurrentUser = msg.arg1;
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index f263b4d..861a379 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -178,6 +178,7 @@
      *  Codec: Codec info.
      *  DisplayText: Display text for the call.
      *  AdditionalCallInfo: Additional call info.
+     *  CallRadioTech: The radio tech on which the call is placed.
      */
     public static final String EXTRA_OI = "oi";
     public static final String EXTRA_CNA = "cna";
@@ -187,6 +188,7 @@
     public static final String EXTRA_CODEC = "Codec";
     public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
     public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
+    public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
 
     public int mServiceType;
     public int mCallType;