Merge "Allow preloading of freefore multi window drawables."
diff --git a/api/current.txt b/api/current.txt
index c58219d..0263927 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13466,6 +13466,7 @@
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_TIMESTAMP_SOURCE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_WHITE_LEVEL;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_MAX_ANALOG_SENSITIVITY;
+    field public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_ORIENTATION;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_REFERENCE_ILLUMINANT1;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> SENSOR_REFERENCE_ILLUMINANT2;
@@ -13878,6 +13879,8 @@
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
     field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
+    field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+    field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> SENSOR_GREEN_SPLIT;
@@ -36774,6 +36777,7 @@
     method public void stopNestedScroll();
     method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
     method public void unscheduleDrawable(android.graphics.drawable.Drawable);
+    method public final void updateDragShadow(android.view.View.DragShadowBuilder);
     method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
     method public boolean willNotCacheDrawing();
     method public boolean willNotDraw();
diff --git a/api/system-current.txt b/api/system-current.txt
index 5b976a9..621a282 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -13814,6 +13814,7 @@
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_TIMESTAMP_SOURCE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_WHITE_LEVEL;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_MAX_ANALOG_SENSITIVITY;
+    field public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_ORIENTATION;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_REFERENCE_ILLUMINANT1;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> SENSOR_REFERENCE_ILLUMINANT2;
@@ -14226,6 +14227,8 @@
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
     field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
+    field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+    field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> SENSOR_GREEN_SPLIT;
@@ -39097,6 +39100,7 @@
     method public void stopNestedScroll();
     method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
     method public void unscheduleDrawable(android.graphics.drawable.Drawable);
+    method public final void updateDragShadow(android.view.View.DragShadowBuilder);
     method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
     method public boolean willNotCacheDrawing();
     method public boolean willNotDraw();
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 0b7b6fc..303078b 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -751,7 +751,8 @@
             int taskId = data.readInt();
             int createMode = data.readInt();
             boolean toTop = data.readInt() != 0;
-            moveTaskToDockedStack(taskId, createMode, toTop);
+            boolean animate = data.readInt() != 0;
+            moveTaskToDockedStack(taskId, createMode, toTop, animate);
             reply.writeNoException();
             return true;
         }
@@ -3577,7 +3578,7 @@
         reply.recycle();
     }
     @Override
-    public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop)
+    public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate)
             throws RemoteException
     {
         Parcel data = Parcel.obtain();
@@ -3586,6 +3587,7 @@
         data.writeInt(taskId);
         data.writeInt(createMode);
         data.writeInt(toTop ? 1 : 0);
+        data.writeInt(animate ? 1 : 0);
         mRemote.transact(MOVE_TASK_TO_DOCKED_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index db4f5c1..6594990 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -142,7 +142,7 @@
     public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
     public void moveTaskBackwards(int task) throws RemoteException;
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException;
-    public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop)
+    public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate)
             throws RemoteException;
     public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) throws RemoteException;
     public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) throws RemoteException;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 848b33f..7fdb972 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3282,12 +3282,17 @@
                     tombstone ? getActionTombstoneLayoutResource()
                               : getActionLayoutResource());
             final Icon ai = action.getIcon();
-            button.setTextViewCompoundDrawablesRelative(R.id.action0, ai, null, null, null);
             button.setTextViewText(R.id.action0, processLegacyText(action.title));
             if (!tombstone) {
                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
             }
             button.setContentDescription(R.id.action0, action.title);
+            if (action.mRemoteInputs != null) {
+                button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
+            }
+            if (mN.color != COLOR_DEFAULT) {
+                button.setTextColor(R.id.action0, mN.color);
+            }
             processLegacyAction(action, button);
             return button;
         }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 68f660f..1c65c94 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2864,24 +2864,6 @@
     }
 
     /**
-     * Returns the device owner user id. Note this method will still return the device owner user id
-     * even if it's running on a different user. If there is no device owner this method return
-     * {@link UserHandle.USER_NULL}.
-     *
-     * @hide
-     */
-    public int getDeviceOwnerUserId() {
-        if (mService != null) {
-            try {
-                return mService.getDeviceOwnerUserId();
-            } catch (RemoteException re) {
-                Log.w(TAG, "Failed to get device owner user id");
-            }
-        }
-        return UserHandle.USER_NULL;
-    }
-
-    /**
      * @hide
      * @deprecated Do not use
      * @removed
@@ -4576,4 +4558,37 @@
             return false;
         }
     }
+
+    /**
+     * @hide
+     * Return if this user is a managed profile of another user. An admin can become the profile
+     * owner of a managed profile with {@link #ACTION_PROVISION_MANAGED_PROFILE} and of a managed
+     * user with {@link #ACTION_PROVISION_MANAGED_USER}.
+     * @param admin Which profile owner this request is associated with.
+     * @return if this user is a managed profile of another user.
+     */
+    public boolean isManagedProfile(@NonNull ComponentName admin) {
+        try {
+            return mService.isManagedProfile(admin);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Failed talking with device policy service", re);
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     * Return if this user is a system-only user. An admin can manage a device from a system only
+     * user by calling {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE}.
+     * @param admin Which device owner this request is associated with.
+     * @return if this user is a system-only user.
+     */
+    public boolean isSystemOnlyUser(@NonNull ComponentName admin) {
+        try {
+            return mService.isSystemOnlyUser(admin);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Failed talking with device policy service", re);
+            return false;
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ba34c3d..fc7c2b3 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -116,7 +116,6 @@
     boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
     ComponentName getDeviceOwner();
     String getDeviceOwnerName();
-    int getDeviceOwnerUserId();
     void clearDeviceOwner(String packageName);
 
     boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle);
@@ -234,4 +233,6 @@
     boolean isProvisioningAllowed(String action);
     void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
     List<String> getKeepUninstalledPackages(in ComponentName admin);
+    boolean isManagedProfile(in ComponentName admin);
+    boolean isSystemOnlyUser(in ComponentName admin);
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a2ef078..6fc998f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2178,11 +2178,18 @@
      * (8-14 bits is expected), or by the point where the sensor response
      * becomes too non-linear to be useful.  The default value for this is
      * maximum representable value for a 16-bit raw sample (2^16 - 1).</p>
+     * <p>The white level values of captured images may vary for different
+     * capture settings (e.g., {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}). This key
+     * represents a coarse approximation for such case. It is recommended
+     * to use {@link CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL android.sensor.dynamicWhiteLevel} for captures when supported
+     * by the camera device, which provides more accurate white level values.</p>
      * <p><b>Range of valid values:</b><br>
      * &gt; 255 (8-bit output)</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+     * @see CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL
+     * @see CaptureRequest#SENSOR_SENSITIVITY
      */
     @PublicKey
     public static final Key<Integer> SENSOR_INFO_WHITE_LEVEL =
@@ -2520,12 +2527,24 @@
      * layout key (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the
      * nth value given corresponds to the black level offset for the nth
      * color channel listed in the CFA.</p>
+     * <p>The black level values of captured images may vary for different
+     * capture settings (e.g., {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}). This key
+     * represents a coarse approximation for such case. It is recommended to
+     * use {@link CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL android.sensor.dynamicBlackLevel} or use pixels from
+     * {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} directly for captures when
+     * supported by the camera device, which provides more accurate black
+     * level values. For raw capture in particular, it is recommended to use
+     * pixels from {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} to calculate black
+     * level values for each frame.</p>
      * <p><b>Range of valid values:</b><br>
      * &gt;= 0 for each.</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
+     * @see CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL
      * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
      * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
+     * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+     * @see CaptureRequest#SENSOR_SENSITIVITY
      */
     @PublicKey
     public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN =
@@ -2580,6 +2599,32 @@
             new Key<int[]>("android.sensor.availableTestPatternModes", int[].class);
 
     /**
+     * <p>List of disjoint rectangles indicating the sensor
+     * optically shielded black pixel regions.</p>
+     * <p>In most camera sensors, the active array is surrounded by some
+     * optically shielded pixel areas. By blocking light, these pixels
+     * provides a reliable black reference for black level compensation
+     * in active array region.</p>
+     * <p>This key provides a list of disjoint rectangles specifying the
+     * regions of optically shielded (with metal shield) black pixel
+     * regions if the camera device is capable of reading out these black
+     * pixels in the output raw images. In comparison to the fixed black
+     * level values reported by {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern}, this key
+     * may provide a more accurate way for the application to calculate
+     * black level of each captured raw images.</p>
+     * <p>When this key is reported, the {@link CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL android.sensor.dynamicBlackLevel} and
+     * {@link CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL android.sensor.dynamicWhiteLevel} will also be reported.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+     * @see CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL
+     * @see CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL
+     */
+    @PublicKey
+    public static final Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS =
+            new Key<android.graphics.Rect[]>("android.sensor.opticalBlackRegions", android.graphics.Rect[].class);
+
+    /**
      * <p>List of lens shading modes for {@link CaptureRequest#SHADING_MODE android.shading.mode} that are supported by this camera device.</p>
      * <p>This list contains lens shading modes that can be set for the camera device.
      * Camera devices that support the MANUAL_POST_PROCESSING capability will always
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index b3acf2b..5f27bca 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3292,6 +3292,69 @@
             new Key<Long>("android.sensor.rollingShutterSkew", long.class);
 
     /**
+     * <p>A per-frame dynamic black level offset for each of the color filter
+     * arrangement (CFA) mosaic channels.</p>
+     * <p>Camera sensor black levels may vary dramatically for different
+     * capture settings (e.g. {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}). The fixed black
+     * level reported by {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} may be too
+     * inaccurate to represent the actual value on a per-frame basis. The
+     * camera device internal pipeline relies on reliable black level values
+     * to process the raw images appropriately. To get the best image
+     * quality, the camera device may choose to estimate the per frame black
+     * level values either based on optically shielded black regions
+     * ({@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions}) or its internal model.</p>
+     * <p>This key reports the camera device estimated per-frame zero light
+     * value for each of the CFA mosaic channels in the camera sensor. The
+     * {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} may only represent a coarse
+     * approximation of the actual black level values. This value is the
+     * black level used in camera device internal image processing pipeline
+     * and generally more accurate than the fixed black level values.
+     * However, since they are estimated values by the camera device, they
+     * may not be as accurate as the black level values calculated from the
+     * optical black pixels reported by {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions}.</p>
+     * <p>The values are given in the same order as channels listed for the CFA
+     * layout key (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the
+     * nth value given corresponds to the black level offset for the nth
+     * color channel listed in the CFA.</p>
+     * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is
+     * available or the camera device advertises this key via
+     * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureRequestKeys }.</p>
+     * <p><b>Range of valid values:</b><br>
+     * &gt;= 0 for each.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+     * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+     * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+     * @see CaptureRequest#SENSOR_SENSITIVITY
+     */
+    @PublicKey
+    public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL =
+            new Key<android.hardware.camera2.params.BlackLevelPattern>("android.sensor.dynamicBlackLevel", android.hardware.camera2.params.BlackLevelPattern.class);
+
+    /**
+     * <p>Maximum raw value output by sensor for this frame.</p>
+     * <p>Since the android.sensor.blackLevel may change for different
+     * capture settings (e.g., {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}), the white
+     * level will change accordingly. This key is similar to
+     * {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}, but specifies the camera device
+     * estimated white level for each frame.</p>
+     * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is
+     * available or the camera device advertises this key via
+     * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureRequestKeys }.</p>
+     * <p><b>Range of valid values:</b><br>
+     * &gt;= 0</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
+     * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+     * @see CaptureRequest#SENSOR_SENSITIVITY
+     */
+    @PublicKey
+    public static final Key<Integer> SENSOR_DYNAMIC_WHITE_LEVEL =
+            new Key<Integer>("android.sensor.dynamicWhiteLevel", int.class);
+
+    /**
      * <p>Quality of lens shading correction applied
      * to the image data.</p>
      * <p>When set to OFF mode, no lens shading correction will be applied by the
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b5bbbbb..c71d6cc 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -56,7 +56,6 @@
     Bundle getUserRestrictions(int userHandle);
     boolean hasUserRestriction(in String restrictionKey, 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);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7b5f5ab..64a046e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -289,8 +289,12 @@
 
     /**
      * Create a screenshot of the applications currently displayed.
+     *
+     * @param frameScale the scale to apply to the frame, only used when width = -1 and 
+     *                   height = -1
      */
-    Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
+    Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight, 
+            float frameScale);
 
     /**
      * Called by the status bar to notify Views of changes to System UI visiblity.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 66381f9..ab1943c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -807,6 +807,12 @@
     private static boolean sAlwaysRemeasureExactly = false;
 
     /**
+     * Relax constraints around whether setLayoutParams() must be called after
+     * modifying the layout params.
+     */
+    private static boolean sLayoutParamsAlwaysChanged = false;
+
+    /**
      * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
      * calling setFlags.
      */
@@ -3975,6 +3981,11 @@
             // modes, so we always need to run an additional EXACTLY pass.
             sAlwaysRemeasureExactly = targetSdkVersion <= M;
 
+            // Prior to N, layout params could change without requiring a
+            // subsequent call to setLayoutParams() and they would usually
+            // work. Partial layout breaks this assumption.
+            sLayoutParamsAlwaysChanged = targetSdkVersion <= M;
+
             sCompatibilityDone = true;
         }
     }
@@ -16904,6 +16915,9 @@
      * @return true if this view's LayoutParams changed since last layout.
      */
     public final boolean didLayoutParamsChange() {
+        if (sLayoutParamsAlwaysChanged) {
+            return true;
+        }
         return (mPrivateFlags3 & PFLAG3_LAYOUT_PARAMS_CHANGED) == PFLAG3_LAYOUT_PARAMS_CHANGED;
     }
 
@@ -19978,19 +19992,22 @@
             Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y
                     + " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
         }
-        Surface surface = new Surface();
+        if (mAttachInfo.mDragSurface != null) {
+            mAttachInfo.mDragSurface.release();
+        }
+        mAttachInfo.mDragSurface = new Surface();
         try {
             mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
-                    flags, shadowSize.x, shadowSize.y, surface);
+                    flags, shadowSize.x, shadowSize.y, mAttachInfo.mDragSurface);
             if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
-                    + mAttachInfo.mDragToken + " surface=" + surface);
+                    + mAttachInfo.mDragToken + " surface=" + mAttachInfo.mDragSurface);
             if (mAttachInfo.mDragToken != null) {
-                Canvas canvas = surface.lockCanvas(null);
+                Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
                 try {
                     canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                     shadowBuilder.onDrawShadow(canvas);
                 } finally {
-                    surface.unlockCanvasAndPost(canvas);
+                    mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
                 }
 
                 final ViewRootImpl root = getViewRootImpl();
@@ -20005,14 +20022,11 @@
                         shadowSize.x, shadowSize.y,
                         shadowTouchPoint.x, shadowTouchPoint.y, data);
                 if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
-
-                // Off and running!  Release our local surface instance; the drag
-                // shadow surface is now managed by the system process.
-                surface.release();
             }
         } catch (Exception e) {
             Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
-            surface.destroy();
+            mAttachInfo.mDragSurface.destroy();
+            mAttachInfo.mDragSurface = null;
         }
 
         return okay;
@@ -20051,6 +20065,27 @@
         }
     }
 
+    public final void updateDragShadow(DragShadowBuilder shadowBuilder) {
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "updateDragShadow");
+        }
+        if (mAttachInfo.mDragToken != null) {
+            try {
+                Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+                try {
+                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+                    shadowBuilder.onDrawShadow(canvas);
+                } finally {
+                    mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
+                }
+            } catch (Exception e) {
+                Log.e(VIEW_LOG_TAG, "Unable to update drag shadow", e);
+            }
+        } else {
+            Log.e(VIEW_LOG_TAG, "No active drag");
+        }
+    }
+
     /**
      * Starts a move from {startX, startY}, the amount of the movement will be the offset
      * between {startX, startY} and the new cursor positon.
@@ -22342,6 +22377,11 @@
         IBinder mDragToken;
 
         /**
+         * The drag shadow surface for the current drag operation.
+         */
+        public Surface mDragSurface;
+
+        /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
          *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2c0cd7a..b503e12 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5376,6 +5376,10 @@
                 if (what == DragEvent.ACTION_DRAG_ENDED) {
                     setLocalDragState(null);
                     mAttachInfo.mDragToken = null;
+                    if (mAttachInfo.mDragSurface != null) {
+                        mAttachInfo.mDragSurface.release();
+                        mAttachInfo.mDragSurface = null;
+                    }
                 }
             }
         }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ca1b211..ce1c108 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -21,6 +21,7 @@
 import android.app.ActivityThread;
 import android.app.Application;
 import android.app.PendingIntent;
+import android.app.RemoteInput;
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -153,6 +154,13 @@
     };
 
     /**
+     * @hide
+     */
+    public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) {
+        mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
+    }
+
+    /**
      * Handle with care!
      */
     static class MutablePair<F, S> {
@@ -1699,6 +1707,43 @@
     }
 
     /**
+     * Helper action to add a view tag with RemoteInputs.
+     */
+    private class SetRemoteInputsAction extends Action {
+
+        public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) {
+            this.viewId = viewId;
+            this.remoteInputs = remoteInputs;
+        }
+
+        public SetRemoteInputsAction(Parcel parcel) {
+            viewId = parcel.readInt();
+            remoteInputs = parcel.readParcelableArray(RemoteInput.class.getClassLoader());
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeParcelableArray(remoteInputs, flags);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+            final TextView target = (TextView) root.findViewById(viewId);
+            if (target == null) return;
+
+            target.setTagInternal(R.id.remote_input_tag, remoteInputs);
+        }
+
+        public String getActionName() {
+            return "SetRemoteInputsAction";
+        }
+
+        final Parcelable[] remoteInputs;
+        public final static int TAG = 18;
+    }
+
+    /**
      * Simple class used to keep track of memory usage in a RemoteViews.
      *
      */
@@ -1894,6 +1939,9 @@
                         case TextViewDrawableColorFilterAction.TAG:
                             mActions.add(new TextViewDrawableColorFilterAction(parcel));
                             break;
+                        case SetRemoteInputsAction.TAG:
+                            mActions.add(new SetRemoteInputsAction(parcel));
+                            break;
                         default:
                             throw new ActionException("Tag " + tag + " not found");
                     }
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index a24fb40..1d6e52c 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -880,8 +880,12 @@
             public boolean onTouch(View view, MotionEvent motionEvent) {
                 final int actionMasked = motionEvent.getActionMasked();
                 if (actionMasked == MotionEvent.ACTION_DOWN) {
-                    mInitialTouchTarget = findNearestChild((ViewGroup) view,
-                            (int) motionEvent.getX(), (int) motionEvent.getY());
+                    if (view instanceof ViewGroup) {
+                        mInitialTouchTarget = findNearestChild((ViewGroup) view,
+                                (int) motionEvent.getX(), (int) motionEvent.getY());
+                    } else {
+                        mInitialTouchTarget = null;
+                    }
                 }
 
                 final View child = mInitialTouchTarget;
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index da8b2e7..f4bc918 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -18,15 +18,11 @@
 <Button xmlns:android="http://schemas.android.com/apk/res/android"
     style="@android:style/Widget.Material.Light.Button.Borderless.Small"
     android:id="@+id/action0"
-    android:layout_width="0dp"
+    android:layout_width="wrap_content"
     android:layout_height="48dp"
-    android:layout_weight="1"
-    android:layout_margin="0dp"
-    android:gravity="start|center_vertical"
-    android:drawablePadding="8dp"
-    android:paddingStart="8dp"
+    android:layout_gravity="center"
+    android:layout_marginStart="8dp"
     android:textColor="@color/secondary_text_material_light"
-    android:textSize="13sp"
     android:singleLine="true"
     android:ellipsize="end"
     android:background="@drawable/notification_material_action_background"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 2a36949..edaf020 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -14,14 +14,20 @@
      limitations under the License.
 -->
 
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/actions"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:visibility="gone"
-    android:layout_marginBottom="8dp"
-    >
-    <!-- actions will be added here -->
-</LinearLayout>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/actions_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+    <LinearLayout
+            android:id="@+id/actions"
+            android:layout_width="match_parent"
+            android:layout_height="56dp"
+            android:paddingEnd="8dp"
+            android:orientation="horizontal"
+            android:visibility="gone"
+            android:background="#ffeeeeee"
+            >
+        <!-- actions will be added here -->
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/values-sw720dp/config.xml b/core/res/res/values-sw720dp/config.xml
index 9792835..1f5791a 100644
--- a/core/res/res/values-sw720dp/config.xml
+++ b/core/res/res/values-sw720dp/config.xml
@@ -19,4 +19,7 @@
          used for picking activities to handle an intent. -->
     <integer name="config_maxResolverActivityColumns">4</integer>
 
+    <!-- Enable cascading submenus. -->
+    <bool name="config_enableCascadingSubmenus">true</bool>
+
 </resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 9d5e5ac..c03fbeb 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -122,4 +122,6 @@
   
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
   <item type="id" name="accessibilityActionContextClick" />
+
+  <item type="id" name="remote_input_tag" />
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1964bec..1dd502f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3623,6 +3623,8 @@
     <string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
     <!-- Message shown when switching to a user [CHAR LIMIT=none] -->
     <string name="user_switching_message">Switching to <xliff:g id="name" example="Bob">%1$s</xliff:g>\u2026</string>
+    <!-- Message when logging out a user on a split user system -->
+    <string name="user_logging_out_message">Logging out <xliff:g id="name" example="Bob">%1$s</xliff:g>\u2026</string>
     <!-- Default name of the owner user [CHAR LIMIT=20] -->
     <string name="owner_name" msgid="3879126011135546571">Owner</string>
     <!-- Error message title [CHAR LIMIT=35] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 49243e1..c66dd18 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -933,6 +933,7 @@
   <java-symbol type="string" name="upload_file" />
   <java-symbol type="string" name="user_switched" />
   <java-symbol type="string" name="user_switching_message" />
+  <java-symbol type="string" name="user_logging_out_message" />
   <java-symbol type="string" name="volume_alarm" />
   <java-symbol type="string" name="volume_icon_description_bluetooth" />
   <java-symbol type="string" name="volume_icon_description_incall" />
@@ -2304,6 +2305,9 @@
   <java-symbol type="string" name="notification_inbox_ellipsis" />
   <java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" />
 
+  <java-symbol type="id" name="actions_container" />
+  <java-symbol type="id" name="remote_input_tag" />
+
   <java-symbol type="attr" name="seekBarDialogPreferenceStyle" />
   <java-symbol type="string" name="ext_media_status_removed" />
   <java-symbol type="string" name="ext_media_status_unmounted" />
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 27c6620..ab37519 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -143,7 +143,9 @@
          access while in power save mode, even if they aren't in the foreground. -->
     <allow-in-power-save-except-idle package="com.android.providers.downloads" />
 
+    <!-- These are the packages that are white-listed to be able to run as system user -->
+    <system-user-whitelisted-app package="com.android.settings" />
+
     <!-- These are the packages that shouldn't run as system user -->
     <system-user-blacklisted-app package="com.android.wallpaper.livepicker" />
-    <system-user-blacklisted-app package="com.android.settings" />
 </permissions>
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 5ad1bce..f8c6f3f 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -87,7 +87,7 @@
             return;
         }
 
-        long startTime = System.currentTimeMillis();
+        long startTime = System.nanoTime();
         switch (msg.what) {
             case DO_RELEASE: {
                 mTvInputSessionImpl.release();
@@ -185,18 +185,18 @@
                 break;
             }
         }
-        long duration = System.currentTimeMillis() - startTime;
-        if (duration > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
+        long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
+        if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
             Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
-                    + duration + "ms)");
-            if (msg.what == DO_TUNE && duration > EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS) {
-                throw new RuntimeException("Too much time to handle tune request. (" + duration
+                    + durationMs + "ms)");
+            if (msg.what == DO_TUNE && durationMs > EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS) {
+                throw new RuntimeException("Too much time to handle tune request. (" + durationMs
                         + "ms > " + EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS + "ms) "
                         + "Consider handling the tune request in a separate thread.");
             }
-            if (duration > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
+            if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
                 throw new RuntimeException("Too much time to handle a request. (type=" + msg.what +
-                        ", " + duration + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
+                        ", " + durationMs + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
             }
         }
     }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 368f9f7..f2920e5 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -12,12 +12,23 @@
 
 -keep class com.android.systemui.statusbar.phone.PhoneStatusBar
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
--keep class com.android.systemui.recents.*
 
 -keepclassmembers class ** {
     public void onBusEvent(**);
     public void onInterprocessBusEvent(**);
 }
 -keepclassmembers class ** extends **.EventBus$InterprocessEvent {
-	public <init>(android.os.Bundle);
-}
\ No newline at end of file
+    public <init>(android.os.Bundle);
+}
+
+-keep class com.android.systemui.recents.views.TaskStackLayoutAlgorithm {
+    public float getFocusState();
+    public void setFocusState(float);
+}
+
+-keep class com.android.systemui.recents.views.TaskView {
+    public int getDim();
+    public void setDim(int);
+    public float getTaskProgress();
+    public void setTaskProgress(float);
+}
diff --git a/packages/SystemUI/res/drawable/ic_send.xml b/packages/SystemUI/res/drawable/ic_send.xml
new file mode 100644
index 0000000..b1c7914
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_send.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/>
+    <path
+        android:pathData="M0 0h48v48H0z"
+        android:fillColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/recents_dismiss_button.xml b/packages/SystemUI/res/layout/recents_dismiss_button.xml
deleted file mode 100644
index 6a2f782..0000000
--- a/packages/SystemUI/res/layout/recents_dismiss_button.xml
+++ /dev/null
@@ -1,32 +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.
--->
-<!-- Extends Framelayout -->
-<com.android.systemui.statusbar.DismissView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/recents_dismiss_all_button_size"
-        android:visibility="gone"
-        android:clipChildren="false"
-        android:clipToPadding="false">
-    <com.android.systemui.statusbar.DismissViewButton
-            android:id="@+id/dismiss_text"
-            android:layout_width="@dimen/recents_dismiss_all_button_size"
-            android:layout_height="@dimen/recents_dismiss_all_button_size"
-            android:layout_gravity="end"
-            android:alpha="1.0"
-            android:background="@drawable/recents_button_bg"
-            android:contentDescription="@string/recents_dismiss_all_message"/>
-</com.android.systemui.statusbar.DismissView>
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index 8ca5634..74092c1 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -16,31 +16,61 @@
   ~ limitations under the License
   -->
 
-<!-- FrameLayout -->
+<!-- LinearLayout -->
 <com.android.systemui.statusbar.policy.RemoteInputView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:theme="@style/systemui_theme_light"
+        android:theme="@style/systemui_theme_remote_input"
+        android:id="@+id/remote_input"
         android:layout_height="match_parent"
         android:layout_width="match_parent"
-        android:paddingStart="4dp"
-        android:paddingEnd="2dp"
+        android:paddingStart="16dp"
+        android:paddingEnd="12dp"
         android:paddingBottom="4dp"
         android:paddingTop="2dp">
 
     <view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText"
             android:id="@+id/remote_input_text"
-            android:layout_height="wrap_content"
-            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:paddingEnd="12dp"
+            android:gravity="start|center_vertical"
+            android:textAppearance="?android:attr/textAppearance"
+            android:textColor="#deffffff"
+            android:textSize="16sp"
+            android:background="@null"
             android:singleLine="true"
+            android:ellipsize="start"
             android:imeOptions="actionSend" />
 
-    <ProgressBar
-            android:id="@+id/remote_input_progress"
-            android:layout_width="match_parent"
+    <FrameLayout
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="bottom"
-            android:visibility="invisible"
-            android:indeterminate="true"
-            style="?android:attr/progressBarStyleHorizontal" />
+            android:layout_gravity="center_vertical">
+
+        <ImageButton
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:paddingStart="12dp"
+                android:paddingEnd="12dp"
+                android:paddingTop="12dp"
+                android:paddingBottom="12dp"
+                android:id="@+id/remote_input_send"
+                android:src="@drawable/ic_send"
+                android:tint="@android:color/white"
+                android:tintMode="src_atop"
+                android:background="@drawable/ripple_drawable" />
+
+        <ProgressBar
+                android:id="@+id/remote_input_progress"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_gravity="center"
+                android:visibility="invisible"
+                android:indeterminate="true"
+                style="?android:attr/progressBarStyleSmall" />
+
+    </FrameLayout>
 
 </com.android.systemui.statusbar.policy.RemoteInputView>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index f7e2344..43e7bac 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -28,4 +28,14 @@
 
     <!-- We have only space for one notification on phone landscape layouts. -->
     <integer name="keyguard_max_notification_count">1</integer>
+
+    <!-- Recents: The relative range of visible tasks from the current scroll position
+         while the stack is focused. -->
+    <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
+    <item name="recents_layout_focused_range_max" format="float" type="integer">2</item>
+
+    <!-- Recents: The relative range of visible tasks from the current scroll position
+         while the stack is not focused. -->
+    <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+    <item name="recents_layout_unfocused_range_max" format="float" type="integer">1.5</item>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 4f6d209..6795da4 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -32,4 +32,14 @@
 
     <!-- Set to true to enable the user switcher on the keyguard. -->
     <bool name="config_keyguardUserSwitcher">true</bool>
+
+    <!-- Recents: The relative range of visible tasks from the current scroll position
+         while the stack is focused. -->
+    <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+    <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
+
+    <!-- Recents: The relative range of visible tasks from the current scroll position
+         while the stack is not focused. -->
+    <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+    <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0c638a2..d8193ab 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -138,12 +138,6 @@
     <!-- The duration in seconds to wait before the dismiss buttons are shown. -->
     <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>
-
-    <!-- The min animation duration for animating views that are newly visible. -->
-    <integer name="recents_filter_animate_new_views_duration">250</integer>
-
     <!-- The duration of the window transition when coming to Recents from an app.
          In order to defer the in-app animations until after the transition is complete,
          we also need to use this value as the starting delay when animating the first
@@ -192,6 +186,16 @@
     <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
     <integer name="recents_svelte_level">0</integer>
 
+    <!-- Recents: The relative range of visible tasks from the current scroll position
+         while the stack is focused. -->
+    <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+    <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
+
+    <!-- Recents: The relative range of visible tasks from the current scroll position
+         while the stack is not focused. -->
+    <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+    <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
+
     <!-- Whether to enable KeyguardService or not -->
     <bool name="config_enableKeyguardService">true</bool>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 73f63a9..c85ada8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -247,6 +247,12 @@
     <!-- The amount to allow the stack to overscroll. -->
     <dimen name="recents_stack_overscroll">24dp</dimen>
 
+    <!-- The size of the peek area at the top of the stack. -->
+    <dimen name="recents_layout_focused_peek_size">@dimen/recents_history_button_height</dimen>
+
+    <!-- The height of the history button. -->
+    <dimen name="recents_history_button_height">48dp</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 00b10a1..e0c0f6a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -698,8 +698,6 @@
     <string name="recents_search_bar_label">search</string>
     <!-- Recents: Launch error string. [CHAR LIMIT=NONE] -->
     <string name="recents_launch_error_message">Could not start <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
-    <!-- Recents: Dismiss all button. [CHAR LIMIT=NONE] -->
-    <string name="recents_dismiss_all_message">Dismiss all applications</string>
 
     <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
     <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
@@ -1159,10 +1157,20 @@
     <!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
     <string name="qs_paging" translatable="false">Use the new Quick Settings</string>
 
+    <!-- Toggles paging recents via the recents button -->
+    <string name="overview_page_on_toggle">Enable paging</string>
+    <!-- Description for the toggle for fast-toggling recents via the recents button -->
+    <string name="overview_page_on_toggle_desc">Enable paging via the Overview button</string>
+
     <!-- Toggles fast-toggling recents via the recents button -->
     <string name="overview_fast_toggle_via_button">Enable fast toggle</string>
     <!-- Description for the toggle for fast-toggling recents via the recents button -->
-    <string name="overview_fast_toggle_via_button_desc">Enable paging via the Overview button</string>
+    <string name="overview_fast_toggle_via_button_desc">Enable launch timeout while paging</string>
+
+    <!-- Toggles fullscreen screenshots -->
+    <string name="overview_fullscreen_thumbnails">Enable fullscreen screenshots</string>
+    <!-- Description for the toggle for fullscreen screenshots -->
+    <string name="overview_fullscreen_thumbnails_desc">Enable fullscreen screenshots in Overview</string>
 
     <!-- Category in the System UI Tuner settings, where new/experimental
          settings are -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4462a03..2fd0fe5 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -220,9 +220,8 @@
         <item name="android:colorControlActivated">@color/system_accent_color</item>
     </style>
 
-    <style name="systemui_theme_light" parent="@android:style/Theme.DeviceDefault.Light">
-        <item name="android:colorPrimary">@color/system_primary_color</item>
-        <item name="android:colorControlActivated">@color/system_accent_color</item>
+    <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:colorControlActivated">@android:color/white</item>
     </style>
 
     <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 585f3c7..4d07d5f 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -88,10 +88,20 @@
         android:title="@string/overview" >
 
         <com.android.systemui.tuner.TunerSwitch
+            android:key="overview_page_on_toggle"
+            android:title="@string/overview_page_on_toggle"
+            android:summary="@string/overview_page_on_toggle_desc" />
+
+        <com.android.systemui.tuner.TunerSwitch
             android:key="overview_fast_toggle"
             android:title="@string/overview_fast_toggle_via_button"
             android:summary="@string/overview_fast_toggle_via_button_desc" />
 
+        <com.android.systemui.tuner.TunerSwitch
+            android:key="overview_fullscreen_thumbnails"
+            android:title="@string/overview_fullscreen_thumbnails"
+            android:summary="@string/overview_fullscreen_thumbnails_desc" />
+
     </PreferenceScreen>
 
     <SwitchPreference
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 7cfe38e..69e9359 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -24,7 +24,7 @@
 oneway interface IRecentsNonSystemUserCallbacks {
     void preloadRecents();
     void cancelPreloadingRecents();
-    void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents);
+    void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate);
     void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
     void toggleRecents();
     void onConfigurationChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 348bd87..186cace 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -214,14 +214,15 @@
 
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
+            mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */, true /* animate */);
         } else {
             if (mSystemUserCallbacks != null) {
                 IRecentsNonSystemUserCallbacks callbacks =
                         mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
                 if (callbacks != null) {
                     try {
-                        callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
+                        callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
+                                true /* animate */);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Callback failed", e);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 04def86..92978f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -43,9 +43,9 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
@@ -53,9 +53,12 @@
 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.AllTaskViewsDismissedEvent;
 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.StackViewScrolledEvent;
+import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 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;
@@ -79,40 +82,40 @@
 /**
  * The main Recents activity that is started from AlternateRecentsComponent.
  */
-public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
-        ViewTreeObserver.OnPreDrawListener {
+public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener {
 
     private final static String TAG = "RecentsActivity";
     private final static boolean DEBUG = false;
 
     public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
 
-    RecentsPackageMonitor mPackageMonitor;
-    long mLastTabKeyEventTime;
-    boolean mFinishedOnStartup;
+    private RecentsPackageMonitor mPackageMonitor;
+    private long mLastTabKeyEventTime;
+    private boolean mFinishedOnStartup;
+    private boolean mIgnoreAltTabRelease;
 
     // Top level views
-    RecentsView mRecentsView;
-    SystemBarScrimViews mScrimViews;
-    ViewStub mEmptyViewStub;
-    View mEmptyView;
+    private RecentsView mRecentsView;
+    private SystemBarScrimViews mScrimViews;
+    private ViewStub mEmptyViewStub;
+    private View mEmptyView;
 
     // Resize task debug
-    RecentsResizeTaskDialog mResizeTaskDebugDialog;
+    private RecentsResizeTaskDialog mResizeTaskDebugDialog;
 
     // Search AppWidget
-    AppWidgetProviderInfo mSearchWidgetInfo;
-    RecentsAppWidgetHost mAppWidgetHost;
-    RecentsAppWidgetHostView mSearchWidgetHostView;
+    private AppWidgetProviderInfo mSearchWidgetInfo;
+    private RecentsAppWidgetHost mAppWidgetHost;
+    private RecentsAppWidgetHostView mSearchWidgetHostView;
 
     // Runnables to finish the Recents activity
-    FinishRecentsRunnable mFinishLaunchHomeRunnable;
+    private FinishRecentsRunnable mFinishLaunchHomeRunnable;
 
     // The trigger to automatically launch the current task
-    DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
+    private DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
         @Override
         public void run() {
-            dismissRecentsToFocusedTask(false);
+            dismissRecentsToFocusedTask();
         }
     });
 
@@ -259,12 +262,9 @@
     /**
      * Dismisses recents if we are already visible and the intent is to toggle the recents view.
      */
-    boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) {
+    boolean dismissRecentsToFocusedTask() {
         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;
         }
@@ -274,12 +274,9 @@
     /**
      * Dismisses recents if we are already visible and the intent is to toggle the recents view.
      */
-    boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
+    boolean dismissRecentsToFocusedTaskOrHome() {
         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;
             // If none of the other cases apply, then just go Home
@@ -348,7 +345,6 @@
         // Set the Recents layout
         setContentView(R.layout.recents);
         mRecentsView = (RecentsView) findViewById(R.id.recents_view);
-        mRecentsView.setCallbacks(this);
         mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
@@ -406,13 +402,6 @@
             mRecentsView.disableLayersForOneFrame();
         }
 
-        if (launchState.startHidden) {
-            launchState.startHidden = false;
-            mRecentsView.setStackViewVisibility(View.INVISIBLE);
-        } else {
-            mRecentsView.setStackViewVisibility(View.VISIBLE);
-        }
-
         // Notify that recents is now visible
         SystemServicesProxy ssp = Recents.getSystemServices();
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
@@ -441,6 +430,9 @@
     protected void onStop() {
         super.onStop();
 
+        // Reset some states
+        mIgnoreAltTabRelease = false;
+
         // Notify that recents is now hidden
         SystemServicesProxy ssp = Recents.getSystemServices();
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
@@ -511,10 +503,6 @@
                 boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
                         mLastTabKeyEventTime) > altTabKeyDelay;
                 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
-                    // As we iterate to the next/previous task, cancel any current/lagging window
-                    // transition animations
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
-
                     // Focus the next task in the stack
                     final boolean backward = event.isShiftPressed();
                     if (backward) {
@@ -523,6 +511,11 @@
                         EventBus.getDefault().send(new FocusNextTaskViewEvent());
                     }
                     mLastTabKeyEventTime = SystemClock.elapsedRealtime();
+
+                    // In the case of another ALT event, don't ignore the next release
+                    if (event.isAltPressed()) {
+                        mIgnoreAltTabRelease = false;
+                    }
                 }
                 return true;
             }
@@ -557,7 +550,7 @@
     @Override
     public void onBackPressed() {
         // Dismiss Recents to the focused Task or Home
-        dismissRecentsToFocusedTaskOrHome(true);
+        dismissRecentsToFocusedTaskOrHome();
     }
 
     /**** RecentsResizeTaskDialog ****/
@@ -569,23 +562,25 @@
         return mResizeTaskDebugDialog;
     }
 
-    /**** RecentsView.RecentsViewCallbacks Implementation ****/
-
-    @Override
-    public void onAllTaskViewsDismissed() {
-        mFinishLaunchHomeRunnable.run();
-    }
-
     /**** EventBus events ****/
 
     public final void onBusEvent(ToggleRecentsEvent event) {
-        dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
+        dismissRecentsToFocusedTaskOrHome();
     }
 
     public final void onBusEvent(IterateRecentsEvent event) {
         // Focus the next task
         EventBus.getDefault().send(new FocusNextTaskViewEvent());
-        mIterateTrigger.poke();
+
+        // Start dozing after the recents button is clicked
+        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+        if (debugFlags.isFastToggleRecentsEnabled()) {
+            if (!mIterateTrigger.isDozing()) {
+                mIterateTrigger.startDozing();
+            } else {
+                mIterateTrigger.poke();
+            }
+        }
     }
 
     public final void onBusEvent(UserInteractionEvent event) {
@@ -595,10 +590,12 @@
     public final void onBusEvent(HideRecentsEvent event) {
         if (event.triggeredFromAltTab) {
             // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
-            dismissRecentsToFocusedTaskOrHome(false /* checkFilteredStackState */);
+            if (!mIgnoreAltTabRelease) {
+                dismissRecentsToFocusedTaskOrHome();
+            }
         } else if (event.triggeredFromHomeKey) {
             // Otherwise, dismiss Recents to Home
-            dismissRecentsToHome(true /* checkFilteredStackState */);
+            dismissRecentsToHome(true /* animated */);
         } else {
             // Do nothing
         }
@@ -622,31 +619,22 @@
                 });
             }
         }
-        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();
-                RecentsDebugFlags flags = Recents.getDebugFlags();
-                if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
-                    mIterateTrigger.startDozing();
-                }
-            }
-        });
         mRecentsView.startEnterRecentsAnimation(ctx);
         ctx.postAnimationTrigger.decrement();
     }
 
     public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
-        mRecentsView.setStackViewVisibility(View.VISIBLE);
+        EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+        mRecentsView.invalidate();
     }
 
     public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
-        mRecentsView.setStackViewVisibility(View.INVISIBLE);
+        if (mRecentsView.isLastTaskLaunchedFreeform()) {
+            EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false));
+        }
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+        mRecentsView.invalidate();
     }
 
     public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
@@ -687,6 +675,14 @@
         ssp.removeTask(event.task.key.id);
     }
 
+    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
+        // Just go straight home (no animation necessary because there are no more task views)
+        dismissRecentsToHome(false /* animated */);
+
+        // Keep track of all-deletions
+        MetricsLogger.count(this, "overview_task_all_dismissed", 1);
+    }
+
     public final void onBusEvent(ResizeTaskEvent event) {
         getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
     }
@@ -723,6 +719,12 @@
         finish();
     }
 
+    public final void onBusEvent(StackViewScrolledEvent event) {
+        // Once the user has scrolled while holding alt-tab, then we should ignore the release of
+        // the key
+        mIgnoreAltTabRelease = true;
+    }
+
     private void refreshSearchWidgetView() {
         if (mSearchWidgetInfo != null) {
             SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 7f7dbce..a1e5118 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -34,7 +34,7 @@
     public boolean launchedFromSearchHome;
     public boolean launchedReuseTaskStackViews;
     public boolean launchedHasConfigurationChanged;
-    public boolean startHidden;
+    public boolean launchedViaDragGesture;
     public int launchedToTaskId;
     public int launchedNumVisibleTasks;
     public int launchedNumVisibleThumbnails;
@@ -75,7 +75,7 @@
      */
     public int getInitialFocusTaskIndex(int numTasks) {
         RecentsDebugFlags flags = Recents.getDebugFlags();
-        if (flags.isFastToggleRecentsEnabled() && !launchedWithAltTab) {
+        if (flags.isPageOnToggleEnabled() && !launchedWithAltTab) {
             // If we are fast toggling, then focus the next task depending on when you are on home
             // or coming in from another app
             if (launchedFromHome) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 8f952be..440ed6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -180,7 +180,8 @@
      * Constrain the width of the landscape stack to the smallest width of the device.
      */
     private int getInsetToSmallestWidth(int availableWidth) {
-        if (availableWidth > smallestWidth) {
+        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+        if (!debugFlags.isFullscreenThumbnailsEnabled() && (availableWidth > smallestWidth)) {
             return (availableWidth - smallestWidth) / 2;
         }
         return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 6c74a4e..67d7115 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -27,6 +27,8 @@
 public class RecentsDebugFlags implements TunerService.Tunable {
 
     private static final String KEY_FAST_TOGGLE = "overview_fast_toggle";
+    private static final String KEY_PAGE_ON_TOGGLE = "overview_page_on_toggle";
+    private static final String KEY_FULLSCREEN_THUMBNAILS = "overview_fullscreen_thumbnails";
 
     public static class Static {
         // Enables debug drawing for the transition thumbnail
@@ -47,8 +49,9 @@
         public static final int SystemServicesProxyMockTaskCount = 100;
     }
 
-    private boolean mForceEnableFreeformWorkspace;
-    private boolean mEnableFastToggleRecents;
+    private boolean mFastToggleRecents;
+    private boolean mPageOnToggle;
+    private boolean mUseFullscreenThumbnails;
 
     /**
      * We read the prefs once when we start the activity, then update them as the tuner changes
@@ -57,21 +60,44 @@
     public RecentsDebugFlags(Context context) {
         // Register all our flags, this will also call onTuningChanged() for each key, which will
         // initialize the current state of each flag
-        TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE);
+        TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE, KEY_PAGE_ON_TOGGLE,
+                KEY_FULLSCREEN_THUMBNAILS);
     }
 
     /**
      * @return whether we are enabling fast toggling.
      */
     public boolean isFastToggleRecentsEnabled() {
-        return mEnableFastToggleRecents;
+        return mPageOnToggle && mFastToggleRecents;
+    }
+
+    /**
+     * @return whether the recents button toggles pages.
+     */
+    public boolean isPageOnToggleEnabled() {
+        return mPageOnToggle;
+    }
+
+    /**
+     * @return whether we should show fullscreen thumbnails
+     */
+    public boolean isFullscreenThumbnailsEnabled() {
+        return mUseFullscreenThumbnails;
     }
 
     @Override
     public void onTuningChanged(String key, String newValue) {
         switch (key) {
             case KEY_FAST_TOGGLE:
-                mEnableFastToggleRecents = (newValue != null) &&
+                mFastToggleRecents = (newValue != null) &&
+                        (Integer.parseInt(newValue) != 0);
+                break;
+            case KEY_PAGE_ON_TOGGLE:
+                mPageOnToggle = (newValue != null) &&
+                        (Integer.parseInt(newValue) != 0);
+                break;
+            case KEY_FULLSCREEN_THUMBNAILS:
+                mUseFullscreenThumbnails = (newValue != null) &&
                         (Integer.parseInt(newValue) != 0);
                 break;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 618eb6f..e0ff4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -35,6 +35,7 @@
 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;
@@ -167,7 +168,7 @@
         public void run() {
             // When this fires, then the user has not released alt-tab for at least
             // FAST_ALT_TAB_DELAY_MS milliseconds
-            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */);
+            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */);
         }
     });
 
@@ -250,7 +251,8 @@
     }
 
     @Override
-    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents) {
+    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
+            boolean animate) {
         mTriggeredFromAltTab = triggeredFromAltTab;
         mDraggingInRecents = draggingInRecents;
         if (mFastAltTabTrigger.hasTriggered()) {
@@ -283,7 +285,7 @@
             ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
             MutableBoolean isTopTaskHome = new MutableBoolean(true);
             if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
-                startRecentsActivity(topTask, isTopTaskHome.value);
+                startRecentsActivity(topTask, isTopTaskHome.value, animate);
             }
         } catch (ActivityNotFoundException e) {
             Log.e(TAG, "Failed to launch RecentsActivity", e);
@@ -329,7 +331,7 @@
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
                 RecentsDebugFlags flags = Recents.getDebugFlags();
-                if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
+                if (flags.isPageOnToggleEnabled() && !launchState.launchedWithAltTab) {
                     // Notify recents to move onto the next task
                     EventBus.getDefault().post(new IterateRecentsEvent());
                 } else {
@@ -355,7 +357,7 @@
                 }
 
                 // Otherwise, start the recents activity
-                startRecentsActivity(topTask, isTopTaskHome.value);
+                startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */);
                 mLastToggleTime = SystemClock.elapsedRealtime();
             }
         } catch (ActivityNotFoundException e) {
@@ -539,9 +541,9 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
         if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
-            ssp.startTaskInDockedMode(topTask.id,
+            ssp.moveTaskToDockedStack(topTask.id,
                     ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
-            showRecents(false /* triggeredFromAltTab */, draggingInRecents);
+            showRecents(false /* triggeredFromAltTab */, draggingInRecents, false /* animate */);
         }
     }
 
@@ -791,18 +793,19 @@
      * Shows the recents activity
      */
     private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
-            boolean isTopTaskHome) {
+            boolean isTopTaskHome, boolean animate) {
         RecentsTaskLoader loader = Recents.getTaskLoader();
 
         // Update the header bar if necessary
         reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
 
-        if (sInstanceLoadPlan == null) {
-            // Create a new load plan if onPreloadRecents() was never triggered
+        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
+        // should always preload the tasks now
+        if (mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
+            // Create a new load plan if preloadRecents() was never triggered
             sInstanceLoadPlan = loader.createLoadPlan(mContext);
         }
-
-        if (!sInstanceLoadPlan.hasTasks()) {
+        if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
             loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
         }
         TaskStack stack = sInstanceLoadPlan.getTaskStack();
@@ -811,6 +814,14 @@
         mDummyStackView.updateLayoutForStack(stack);
         TaskStackLayoutAlgorithm.VisibilityReport stackVr =
                 mDummyStackView.computeStackVisibilityReport();
+
+        if (!animate) {
+            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
+            startRecentsActivity(topTask, opts, false /* fromHome */,
+                    false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
+            return;
+        }
+
         boolean hasRecentTasks = stack.getTaskCount() > 0;
         boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
 
@@ -870,8 +881,7 @@
         launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
         launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
         launchState.launchedHasConfigurationChanged = false;
-        launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID
-                || mDraggingInRecents;
+        launchState.launchedViaDragGesture = mDraggingInRecents;
 
         Intent intent = new Intent();
         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
index 9d96d8e..f9ccfc8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.misc.SystemServicesProxy;
 
 /**
  * This is sent when we want to start screen pinning.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java
new file mode 100644
index 0000000..cf74519
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.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;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent whenever all the task views in a stack have been dismissed.
+ */
+public class AllTaskViewsDismissedEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
new file mode 100644
index 0000000..cb5011a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
@@ -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.
+ */
+
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent whenever a new scroll gesture happens on a stack view.
+ */
+public class StackViewScrolledEvent extends EventBus.Event {
+
+    public final int yMovement;
+
+    public StackViewScrolledEvent(int yMovement) {
+        this.yMovement = yMovement;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
new file mode 100644
index 0000000..b42da9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
@@ -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.
+ */
+
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent to update the visibility of all visible freeform task views.
+ */
+public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event {
+
+    public final boolean visible;
+
+    public UpdateFreeformTaskViewVisibilityEvent(boolean visible) {
+        this.visible = visible;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java
new file mode 100644
index 0000000..720c952
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java
@@ -0,0 +1,177 @@
+/*
+ * 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.misc;
+
+import android.graphics.Path;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * An interpolator that can traverse a Path. The x coordinate along the <code>Path</code>
+ * is the input value and the output is the y coordinate of the line at that point.
+ * This means that the Path must conform to a function <code>y = f(x)</code>.
+ *
+ * <p>The <code>Path</code> must not have gaps in the x direction and must not
+ * loop back on itself such that there can be two points sharing the same x coordinate.
+ * It is alright to have a disjoint line in the vertical direction:</p>
+ * <p><blockquote><pre>
+ *     Path path = new Path();
+ *     path.lineTo(0.25f, 0.25f);
+ *     path.moveTo(0.25f, 0.5f);
+ *     path.lineTo(1f, 1f);
+ * </pre></blockquote></p>
+ */
+public class FreePathInterpolator extends BaseInterpolator {
+
+    // This governs how accurate the approximation of the Path is.
+    private static final float PRECISION = 0.002f;
+
+    private float[] mX;
+    private float[] mY;
+    private float mArcLength;
+
+    /**
+     * Create an interpolator for an arbitrary <code>Path</code>.
+     *
+     * @param path The <code>Path</code> to use to make the line representing the interpolator.
+     */
+    public FreePathInterpolator(Path path) {
+        initPath(path);
+    }
+
+    private void initPath(Path path) {
+        float[] pointComponents = path.approximate(PRECISION);
+
+        int numPoints = pointComponents.length / 3;
+
+        mX = new float[numPoints];
+        mY = new float[numPoints];
+        mArcLength = 0;
+        float prevX = 0;
+        float prevY = 0;
+        float prevFraction = 0;
+        int componentIndex = 0;
+        for (int i = 0; i < numPoints; i++) {
+            float fraction = pointComponents[componentIndex++];
+            float x = pointComponents[componentIndex++];
+            float y = pointComponents[componentIndex++];
+            if (fraction == prevFraction && x != prevX) {
+                throw new IllegalArgumentException(
+                        "The Path cannot have discontinuity in the X axis.");
+            }
+            if (x < prevX) {
+                throw new IllegalArgumentException("The Path cannot loop back on itself.");
+            }
+            mX[i] = x;
+            mY[i] = y;
+            mArcLength += Math.hypot(x - prevX, y - prevY);
+            prevX = x;
+            prevY = y;
+            prevFraction = fraction;
+        }
+    }
+
+    /**
+     * Using the line in the Path in this interpolator that can be described as
+     * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
+     * as the x coordinate.
+     *
+     * @param t Treated as the x coordinate along the line.
+     * @return The y coordinate of the Path along the line where x = <code>t</code>.
+     * @see Interpolator#getInterpolation(float)
+     */
+    @Override
+    public float getInterpolation(float t) {
+        int startIndex = 0;
+        int endIndex = mX.length - 1;
+
+        // Return early if out of bounds
+        if (t <= 0) {
+            return mY[startIndex];
+        } else if (t >= 1) {
+            return mY[endIndex];
+        }
+
+        // Do a binary search for the correct x to interpolate between.
+        while (endIndex - startIndex > 1) {
+            int midIndex = (startIndex + endIndex) / 2;
+            if (t < mX[midIndex]) {
+                endIndex = midIndex;
+            } else {
+                startIndex = midIndex;
+            }
+        }
+
+        float xRange = mX[endIndex] - mX[startIndex];
+        if (xRange == 0) {
+            return mY[startIndex];
+        }
+
+        float tInRange = t - mX[startIndex];
+        float fraction = tInRange / xRange;
+
+        float startY = mY[startIndex];
+        float endY = mY[endIndex];
+        return startY + (fraction * (endY - startY));
+    }
+
+    /**
+     * Finds the x that provides the given <code>y = f(x)</code>.
+     *
+     * @param y a value from (0,1) that is in this path.
+     */
+    public float getX(float y) {
+        int startIndex = 0;
+        int endIndex = mY.length - 1;
+
+        // Return early if out of bounds
+        if (y <= 0) {
+            return mX[endIndex];
+        } else if (y >= 1) {
+            return mX[startIndex];
+        }
+
+        // Do a binary search for index that bounds the y
+        while (endIndex - startIndex > 1) {
+            int midIndex = (startIndex + endIndex) / 2;
+            if (y < mY[midIndex]) {
+                startIndex = midIndex;
+            } else {
+                endIndex = midIndex;
+            }
+        }
+
+        float yRange = mY[endIndex] - mY[startIndex];
+        if (yRange == 0) {
+            return mX[startIndex];
+        }
+
+        float tInRange = y - mY[startIndex];
+        float fraction = tInRange / yRange;
+
+        float startX = mX[startIndex];
+        float endX = mX[endIndex];
+        return startX + (fraction * (endX - startX));
+    }
+
+    /**
+     * Returns the arclength of the path we are interpolating.
+     */
+    public float getArcLength() {
+        return mArcLength;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
deleted file mode 100644
index 515c3bd..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * Represents a 2d curve that is parameterized along the arc length of the curve (p), and allows the
- * conversions of x->p and p->x.
- */
-public class ParametricCurve {
-
-    private static final boolean DEBUG = false;
-    private static final String TAG = "ParametricCurve";
-
-    private static final int PrecisionSteps = 250;
-
-    /**
-     * A 2d function, representing the curve.
-     */
-    public interface CurveFunction {
-        float f(float x);
-        float invF(float y);
-    }
-
-    /**
-     * A function that returns a value given a parametric value.
-     */
-    public interface ParametricCurveFunction {
-        float f(float p);
-    }
-
-    float[] xp;
-    float[] px;
-    float mLength;
-
-    CurveFunction mFn;
-    ParametricCurveFunction mScaleFn;
-
-    public ParametricCurve(CurveFunction fn, ParametricCurveFunction scaleFn) {
-        long t1;
-        if (DEBUG) {
-            t1 = SystemClock.currentThreadTimeMicro();
-            Log.d(TAG, "initializeCurve");
-        }
-        mFn = fn;
-        mScaleFn = scaleFn;
-        xp = new float[PrecisionSteps + 1];
-        px = new float[PrecisionSteps + 1];
-
-        // Approximate f(x)
-        float[] fx = new float[PrecisionSteps + 1];
-        float step = 1f / PrecisionSteps;
-        float x = 0;
-        for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
-            fx[xStep] = fn.f(x);
-            x += step;
-        }
-        // Calculate the arc length for x:1->0
-        float pLength = 0;
-        float[] dx = new float[PrecisionSteps + 1];
-        dx[0] = 0;
-        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
-            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;
-        px[PrecisionSteps] = 1f;
-        if (DEBUG) {
-            Log.d(TAG, "p[0]=0");
-            Log.d(TAG, "p[" + PrecisionSteps + "]=1");
-        }
-        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
-            p += Math.abs(dx[xStep] / pLength);
-            px[xStep] = p;
-            if (DEBUG) {
-                Log.d(TAG, "p[" + xStep + "]=" + p);
-            }
-        }
-        // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
-        // function.
-        int xStep = 0;
-        p = 0;
-        xp[0] = 0f;
-        xp[PrecisionSteps] = 1f;
-        if (DEBUG) {
-            Log.d(TAG, "x[0]=0");
-            Log.d(TAG, "x[" + PrecisionSteps + "]=1");
-        }
-        for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
-            // Walk forward in px and find the x where px <= p && p < px+1
-            while (xStep < PrecisionSteps) {
-                if (px[xStep] > p) break;
-                xStep++;
-            }
-            // Now, px[xStep-1] <= p < px[xStep]
-            if (xStep == 0) {
-                xp[pStep] = 0;
-            } else {
-                // Find x such that proportionally, x is correct
-                float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
-                x = (xStep - 1 + fraction) * step;
-                xp[pStep] = x;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "x[" + pStep + "]=" + xp[pStep]);
-            }
-            p += step;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
-        }
-    }
-
-    /**
-     * Converts from the progress along the arc-length of the curve to a coordinate within the
-     * bounds.  Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
-     */
-    public int pToX(float p, Rect bounds) {
-        int top = bounds.top;
-        int height = bounds.height();
-
-        if (p <= 0f) return top;
-        if (p >= 1f) return top + (int) (p * height);
-
-        float pIndex = p * PrecisionSteps;
-        int pFloorIndex = (int) Math.floor(pIndex);
-        int pCeilIndex = (int) Math.ceil(pIndex);
-        float x = xp[pFloorIndex];
-        if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
-            // Interpolate between the two precalculated positions
-            x += (xp[pCeilIndex] - xp[pFloorIndex]) * (pIndex - pFloorIndex);
-        }
-        return top + (int) (x * height);
-    }
-
-    /**
-     * Converts from the progress along the arc-length of the curve to a scale.
-     */
-    public float pToScale(float p) {
-        return mScaleFn.f(p);
-    }
-
-    /**
-     * Converts from a bounds coordinate to the progress along the arc-length of the curve.
-     * Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
-     */
-    public float xToP(int x, Rect bounds) {
-        int top = bounds.top;
-
-        float xf = (float) (x - top) / bounds.height();
-        if (xf <= 0f) return 0f;
-        if (xf >= 1f) return xf;
-
-        float xIndex = xf * PrecisionSteps;
-        int xFloorIndex = (int) Math.floor(xIndex);
-        int xCeilIndex = (int) Math.ceil(xIndex);
-        float p = px[xFloorIndex];
-        if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
-            // Interpolate between the two precalculated positions
-            p += (px[xCeilIndex] - px[xFloorIndex]) * (xIndex - xFloorIndex);
-        }
-        return p;
-    }
-
-    /**
-     * Computes the progress offset from the bottom of the curve (p=1) such that the given height
-     * is visible when scaled at the that progress.
-     */
-    public float computePOffsetForScaledHeight(int height, Rect bounds) {
-        int top = bounds.top;
-        int bottom = bounds.bottom;
-        height = Math.min(height, bottom - top);
-
-        if (bounds.height() == 0) {
-            return 0;
-        }
-
-        // Find the next p(x) such that (bottom-x) == scale(p(x))*height
-        int minX = top;
-        int maxX = bottom;
-        long t1;
-        if (DEBUG) {
-            t1 = SystemClock.currentThreadTimeMicro();
-            Log.d(TAG, "computePOffsetForScaledHeight: " + height);
-        }
-        while (minX <= maxX) {
-            int midX = minX + ((maxX - minX) / 2);
-            float pMidX = xToP(midX, bounds);
-            float scaleMidX = mScaleFn.f(pMidX);
-            int scaledHeight = (int) (scaleMidX * height);
-            if ((bottom - midX) < scaledHeight) {
-                maxX = midX - 1;
-            } else if ((bottom - midX) > scaledHeight) {
-                minX = midX + 1;
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
-                }
-                return 1f - pMidX;
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
-        }
-        return 1f - xToP(maxX, bounds);
-    }
-
-    /**
-     * Computes the progress offset from the bottom of the curve (p=1) that allows the given height,
-     * unscaled at the progress, will be visible.
-     */
-    public float computePOffsetForHeight(int height, Rect bounds) {
-        int top = bounds.top;
-        int bottom = bounds.bottom;
-        height = Math.min(height, bottom - top);
-
-        if (bounds.height() == 0) {
-            return 0;
-        }
-
-        // Find the next p(x) such that (bottom-x) == height
-        int minX = top;
-        int maxX = bottom;
-        long t1;
-        if (DEBUG) {
-            t1 = SystemClock.currentThreadTimeMicro();
-            Log.d(TAG, "computePOffsetForHeight: " + height);
-        }
-        while (minX <= maxX) {
-            int midX = minX + ((maxX - minX) / 2);
-            if ((bottom - midX) < height) {
-                maxX = midX - 1;
-            } else if ((bottom - midX) > height) {
-                minX = midX + 1;
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
-                }
-                return 1f - xToP(midX, bounds);
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
-        }
-        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 16d6929..5026c790 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -311,6 +311,17 @@
         }
     }
 
+    /** Docks an already resumed task to the side of the screen. */
+    public void moveTaskToDockedStack(int taskId, int createMode) {
+        if (mIam == null) return;
+
+        try {
+            mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */, false /* animate */);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
     /** Returns the focused stack id. */
     public int getFocusedStack() {
         if (mIam == null) return -1;
@@ -331,6 +342,13 @@
     }
 
     /**
+     * Returns whether the given stack id is the docked stack id.
+     */
+    public static boolean isDockedStack(int stackId) {
+        return stackId == DOCKED_STACK_ID;
+    }
+
+    /**
      * Returns whether the given stack id is the freeform workspace stack id.
      */
     public static boolean isFreeformStack(int stackId) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index f26dcde..0970252 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -59,7 +59,7 @@
 class FilteredTaskList {
 
     private static final String TAG = "FilteredTaskList";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     ArrayList<Task> mTasks = new ArrayList<>();
     ArrayList<Task> mFilteredTasks = new ArrayList<>();
@@ -74,18 +74,17 @@
         if (!prevFilteredTasks.equals(mFilteredTasks)) {
             return true;
         } else {
-            // If the tasks are exactly the same pre/post filter, then just reset it
-            mFilter = null;
             return false;
         }
     }
 
-    /** Resets this FilteredTaskList. */
+    /**
+     * Resets the task list, but does not remove the filter.
+     */
     void reset() {
         mTasks.clear();
         mFilteredTasks.clear();
         mTaskIndices.clear();
-        mFilter = null;
     }
 
     /** Removes the task filter and returns the previous touch state */
@@ -200,16 +199,10 @@
     /** Task stack callbacks */
     public interface TaskStackCallbacks {
         /* Notifies when a task has been added to the stack */
-        public void onStackTaskAdded(TaskStack stack, Task t);
+        void onStackTaskAdded(TaskStack stack, Task t);
         /* Notifies when a task has been removed from the stack */
-        public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
+        void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
                                        Task newFrontMostTask);
-        /* Notifies when all task has been removed from the stack */
-        public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks);
-        /** Notifies when the stack was filtered */
-        public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
-        /** Notifies when the stack was un-filtered */
-        public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
     }
 
 
@@ -300,8 +293,18 @@
     FilteredTaskList mTaskList = new FilteredTaskList();
     TaskStackCallbacks mCb;
 
-    ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
-    HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
+    ArrayList<TaskGrouping> mGroups = new ArrayList<>();
+    HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<>();
+
+    public TaskStack() {
+        // Ensure that we only show non-docked tasks
+        mTaskList.setFilter(new TaskFilter() {
+            @Override
+            public boolean acceptTask(Task t, int index) {
+                return !SystemServicesProxy.isDockedStack(t.key.stackId);
+            }
+        });
+    }
 
     /** Sets the callbacks for this task stack. */
     public void setCallbacks(TaskStackCallbacks cb) {
@@ -377,20 +380,6 @@
         }
     }
 
-    /** Removes all tasks */
-    public void removeAllTasks() {
-        ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks());
-        int taskCount = taskList.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task t = taskList.get(i);
-            removeTaskImpl(t);
-        }
-        if (mCb != null) {
-            // Notify that all tasks have been removed
-            mCb.onStackAllTasksRemoved(this, taskList);
-        }
-    }
-
     /** Sets a few tasks in one go */
     public void setTasks(List<Task> tasks) {
         ArrayList<Task> taskList = mTaskList.getTasks();
@@ -419,7 +408,7 @@
 
     /** Gets the task keys */
     public ArrayList<Task.TaskKey> getTaskKeys() {
-        ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
+        ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
         ArrayList<Task> tasks = mTaskList.getTasks();
         int taskCount = tasks.size();
         for (int i = 0; i < taskCount; i++) {
@@ -486,41 +475,6 @@
         return false;
     }
 
-    /******** Filtering ********/
-
-    /** Filters the stack into tasks similar to the one specified */
-    public void filterTasks(final Task t) {
-        ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
-
-        // Set the task list filter
-        boolean filtered = mTaskList.setFilter(new TaskFilter() {
-            @Override
-            public boolean acceptTask(Task at, int i) {
-                return t.key.getComponent().getPackageName().equals(
-                        at.key.getComponent().getPackageName());
-            }
-        });
-        if (filtered && mCb != null) {
-            mCb.onStackFiltered(this, oldStack, t);
-        }
-    }
-
-    /** Unfilters the current stack */
-    public void unfilterTasks() {
-        ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
-
-        // Unset the filter, then update the virtual scroll
-        mTaskList.removeFilter();
-        if (mCb != null) {
-            mCb.onStackUnfiltered(this, oldStack);
-        }
-    }
-
-    /** Returns whether tasks are currently filtered */
-    public boolean hasFilteredTasks() {
-        return mTaskList.hasFilter();
-    }
-
     /******** Grouping ********/
 
     /** Adds a group to the set */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index f3c66a5..ff3aef9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -17,7 +17,6 @@
 package com.android.systemui.recents.views;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -49,7 +48,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
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 d18389f..311ee65 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -23,24 +23,23 @@
 import android.os.Handler;
 import android.util.ArraySet;
 import android.util.AttributeSet;
-import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.WindowInsets;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsAppWidgetHostView;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -64,20 +63,13 @@
     private static final String TAG = "RecentsView";
     private static final boolean DEBUG = false;
 
-    private int mStackViewVisibility = View.VISIBLE;
-
-    /** The RecentsView callbacks */
-    public interface RecentsViewCallbacks {
-        public void onAllTaskViewsDismissed();
-    }
-
     LayoutInflater mInflater;
     Handler mHandler;
 
-    ArrayList<TaskStack> mStacks;
     TaskStackView mTaskStackView;
     RecentsAppWidgetHostView mSearchBar;
-    RecentsViewCallbacks mCb;
+    boolean mAwaitingFirstLayout = true;
+    boolean mLastTaskLaunchedWasFreeform;
 
     RecentsTransitionHelper mTransitionHelper;
     RecentsViewTouchHandler mTouchHandler;
@@ -116,11 +108,6 @@
         mTouchHandler = new RecentsViewTouchHandler(this);
     }
 
-    /** Sets the callbacks */
-    public void setCallbacks(RecentsViewCallbacks cb) {
-        mCb = cb;
-    }
-
     /** Set/get the bsp root node */
     public void setTaskStack(TaskStack stack) {
         RecentsConfiguration config = Recents.getConfiguration();
@@ -142,12 +129,18 @@
             mTaskStackView.setCallbacks(this);
             addView(mTaskStackView);
         }
-        mTaskStackView.setVisibility(mStackViewVisibility);
 
         // Trigger a new layout
         requestLayout();
     }
 
+    /**
+     * Returns whether the last task launched was in the freeform stack or not.
+     */
+    public boolean isLastTaskLaunchedFreeform() {
+        return mLastTaskLaunchedWasFreeform;
+    }
+
     /** Gets the next task in the stack - or if the last - the top task */
     public Task getNextTaskOrTopTask(Task taskToSearch) {
         Task returnTask = null;
@@ -336,6 +329,17 @@
             mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
                     top + mDragView.getMeasuredHeight());
         }
+
+        if (mAwaitingFirstLayout) {
+            mAwaitingFirstLayout = false;
+
+            // If launched via dragging from the nav bar, then we should translate the whole view
+            // down offscreen
+            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+            if (launchState.launchedViaDragGesture) {
+                setTranslationY(getMeasuredHeight());
+            }
+        }
     }
 
     @Override
@@ -377,24 +381,6 @@
         return super.verifyDrawable(who);
     }
 
-    /** Unfilters any filtered stacks */
-    public boolean unfilterFilteredStacks() {
-        if (mStacks != null) {
-            // Check if there are any filtered stacks and unfilter them before we back out of Recents
-            boolean stacksUnfiltered = false;
-            int numStacks = mStacks.size();
-            for (int i = 0; i < numStacks; i++) {
-                TaskStack stack = mStacks.get(i);
-                if (stack.hasFilteredTasks()) {
-                    stack.unfilterTasks();
-                    stacksUnfiltered = true;
-                }
-            }
-            return stacksUnfiltered;
-        }
-        return false;
-    }
-
     public void disableLayersForOneFrame() {
         if (mTaskStackView != null) {
             mTaskStackView.disableLayersForOneFrame();
@@ -407,59 +393,11 @@
     public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
             final TaskStack stack, final Task task, final boolean lockToTask,
             final Rect bounds, int destinationStack) {
+        mLastTaskLaunchedWasFreeform = SystemServicesProxy.isFreeformStack(task.key.stackId);
         mTransitionHelper.launchTaskFromRecents(stack, task, stackView, tv, lockToTask, bounds,
                 destinationStack);
     }
 
-    @Override
-    public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) {
-        /* TODO: Not currently enabled
-        if (removedTasks != null) {
-            int taskCount = removedTasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                onTaskViewDismissed(removedTasks.get(i));
-            }
-        }
-        */
-
-        mCb.onAllTaskViewsDismissed();
-
-        // Keep track of all-deletions
-        MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
-    }
-
-    @Override
-    public void onTaskStackFilterTriggered() {
-        // Hide the search bar
-        if (mSearchBar != null) {
-            int filterDuration = getResources().getInteger(
-                    R.integer.recents_filter_animate_current_views_duration);
-            mSearchBar.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setInterpolator(mFastOutSlowInInterpolator)
-                    .setDuration(filterDuration)
-                    .withLayer()
-                    .start();
-        }
-    }
-
-    @Override
-    public void onTaskStackUnfilterTriggered() {
-        // Show the search bar
-        if (mSearchBar != null) {
-            int filterDuration = getResources().getInteger(
-                    R.integer.recents_filter_animate_new_views_duration);
-            mSearchBar.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setInterpolator(mFastOutSlowInInterpolator)
-                    .setDuration(filterDuration)
-                    .withLayer()
-                    .start();
-        }
-    }
-
     /**** EventBus Events ****/
 
     public final void onBusEvent(DragStartEvent event) {
@@ -545,7 +483,6 @@
     }
 
     public final void onBusEvent(DraggingInRecentsEvent event) {
-        setStackViewVisibility(View.VISIBLE);
         setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
     }
 
@@ -579,11 +516,11 @@
         }
     }
 
-    public void setStackViewVisibility(int stackViewVisibility) {
-        mStackViewVisibility = stackViewVisibility;
-        if (mTaskStackView != null) {
-            mTaskStackView.setVisibility(stackViewVisibility);
-            invalidate();
+    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+        if (!event.visible) {
+            // Reset the view state
+            mAwaitingFirstLayout = true;
+            mLastTaskLaunchedWasFreeform = false;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index b3bd6ed..f8f0ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -16,14 +16,22 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Path;
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.util.Log;
+import android.util.Property;
+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.misc.ParametricCurve;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.recents.misc.FreePathInterpolator;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
@@ -32,21 +40,96 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 
+/**
+ * Used to describe a visible range that can be normalized to [0, 1].
+ */
+class Range {
+    final float relativeMin;
+    final float relativeMax;
+    float origin;
+    float min;
+    float max;
+
+    public Range(float relMin, float relMax) {
+        min = relativeMin = relMin;
+        max = relativeMax = relMax;
+    }
+
+    /**
+     * Offsets this range to a given absolute position.
+     */
+    public void offset(float x) {
+        this.origin = x;
+        min = x + relativeMin;
+        max = x + relativeMax;
+    }
+
+    /**
+     * Returns x normalized to the range 0 to 1 such that 0 = min, 0.5 = origin and 1 = max
+     *
+     * @param x is an absolute value in the same domain as origin
+     */
+    public float getNormalizedX(float x) {
+        if (x < origin) {
+            return 0.5f + 0.5f * (x - origin) / -relativeMin;
+        } else {
+            return 0.5f + 0.5f * (x - origin) / relativeMax;
+        }
+    }
+
+    /**
+     * Given a normalized {@param x} value in this range, projected onto the full range to get an
+     * absolute value about the given {@param origin}.
+     */
+    public float getAbsoluteX(float normX) {
+        if (normX < 0.5f) {
+            return (normX - 0.5f) / 0.5f * -relativeMin;
+        } else {
+            return (normX - 0.5f) / 0.5f * relativeMax;
+        }
+    }
+
+    /**
+     * Returns whether a value at an absolute x would be within range.
+     */
+    public boolean isInRange(float absX) {
+        return (absX >= Math.floor(min)) && (absX <= Math.ceil(max));
+    }
+}
 
 /**
- * The layout logic for a TaskStackView.
+ * The layout logic for a TaskStackView.  This layout can have two states focused and unfocused,
+ * and in the focused state, there is a task that is displayed more prominently in the stack.
  */
 public class TaskStackLayoutAlgorithm {
 
     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.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
-    private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
+    // The scale factor to apply to the user movement in the stack to unfocus it
+    private static final float UNFOCUS_MULTIPLIER = 0.8f;
+
+    // The various focus states
+    public static final float STATE_FOCUSED = 1f;
+    public static final float STATE_UNFOCUSED = 0f;
+
+    /**
+     * A Property wrapper around the <code>focusState</code> functionality handled by the
+     * {@link TaskStackLayoutAlgorithm#setFocusState(float)} and
+     * {@link TaskStackLayoutAlgorithm#getFocusState()} methods.
+     */
+    private static final Property<TaskStackLayoutAlgorithm, Float> FOCUS_STATE =
+            new FloatProperty<TaskStackLayoutAlgorithm>("focusState") {
+        @Override
+        public void setValue(TaskStackLayoutAlgorithm object, float value) {
+            object.setFocusState(value);
+        }
+
+        @Override
+        public Float get(TaskStackLayoutAlgorithm object) {
+            return object.getFocusState();
+        }
+    };
 
     // A report of the visibility state of the stack
     public class VisibilityReport {
@@ -61,6 +144,8 @@
     }
 
     Context mContext;
+    private TaskStackView mStackView;
+    private Interpolator mFastOutSlowInInterpolator;
 
     // The task bounds (untransformed) for layout.  This rect is anchored at mTaskRoot.
     public Rect mTaskRect = new Rect();
@@ -74,10 +159,33 @@
     private Rect mStackRect = new Rect();
     // The current stack rect, can either by mFreeformStackRect or mStackRect depending on whether
     // there is a freeform workspace
-    public Rect mCurrentStackRect;
+    public Rect mCurrentStackRect = new Rect();
     // This is the current system insets
     public Rect mSystemInsets = new Rect();
 
+    // The visible ranges when the stack is focused and unfocused
+    private Range mUnfocusedRange;
+    private Range mFocusedRange;
+
+    // The offset from the top when scrolled to the top of the stack
+    private int mFocusedPeekHeight;
+
+    // The offset from the bottom of the stack to the bottom of the bounds
+    private int mStackBottomOffset;
+
+    // The paths defining the motion of the tasks when the stack is focused and unfocused
+    private Path mUnfocusedCurve;
+    private Path mFocusedCurve;
+    private FreePathInterpolator mUnfocusedCurveInterpolator;
+    private FreePathInterpolator mFocusedCurveInterpolator;
+
+    // The state of the stack focus (0..1), which controls the transition of the stack from the
+    // focused to non-focused state
+    private float mFocusState;
+
+    // The animator used to reset the focused state
+    private ObjectAnimator mFocusStateAnimator;
+
     // The smallest scroll progress, at this value, the back most task will be visible
     float mMinScrollP;
     // The largest scroll progress, at this value, the front most task will be visible above the
@@ -88,78 +196,44 @@
     // 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;
-    // The relative progress to ensure that the height between non-affiliated tasks is
-    // respected
-    float mBetweenAffiliationPOffset;
-    // The relative progress to ensure that the task height is respected
-    float mTaskHeightPOffset;
-    // The relative progress to ensure that the half task height is respected
-    float mTaskHalfHeightPOffset;
-    // The front-most task bottom offset
-    int mStackBottomOffset;
-    // The relative progress to ensure that the offset from the bottom of the stack to the bottom
-    // of the task is respected
-    float mStackBottomPOffset;
-
     // The last computed task counts
     int mNumStackTasks;
     int mNumFreeformTasks;
+
     // The min/max z translations
     int mMinTranslationZ;
     int mMaxTranslationZ;
 
-    // Optimization, allows for quick lookup of task -> progress
-    HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
+    // Optimization, allows for quick lookup of task -> index
+    private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
 
     // The freeform workspace layout
     FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
 
-    // Log function
-    static ParametricCurve sCurve;
-
-    public TaskStackLayoutAlgorithm(Context context) {
+    public TaskStackLayoutAlgorithm(Context context, TaskStackView stackView) {
         Resources res = context.getResources();
+        mStackView = stackView;
+
+        mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
+                res.getFloat(R.integer.recents_layout_focused_range_max));
+        mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
+                res.getFloat(R.integer.recents_layout_unfocused_range_max));
+        mFocusState = getDefaultFocusState();
+        mFocusedPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_focused_peek_size);
+
         mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
         mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
         mContext = context;
         mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm();
-        if (sCurve == null) {
-            sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
-                // The large the XScale, the longer the flat area of the curve
-                private static final float XScale = 1.75f;
-                private static final float LogBase = 3000;
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_slow_in);
+    }
 
-                float reverse(float x) {
-                    return (-x * XScale) + 1;
-                }
-
-                @Override
-                public float f(float x) {
-                    return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
-                }
-
-                @Override
-                public float invF(float y) {
-                    return (float) (Math.log(1f - reverse(y)) / (-Math.log(LogBase) * XScale));
-                }
-            }, new ParametricCurve.ParametricCurveFunction() {
-                @Override
-                public float f(float p) {
-                    SystemServicesProxy ssp = Recents.getSystemServices();
-                    if (ssp.hasFreeformWorkspaceSupport()) {
-                        return 1f;
-                    }
-
-                    if (p < 0) return STACK_PEEK_MIN_SCALE;
-                    if (p > 1) return 1f;
-                    float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
-                    float scale = STACK_PEEK_MIN_SCALE + (p * scaleRange);
-                    return scale;
-                }
-            });
-        }
+    /**
+     * Resets this layout when the stack view is reset.
+     */
+    public void reset() {
+        setFocusState(getDefaultFocusState());
     }
 
     /**
@@ -173,16 +247,32 @@
     }
 
     /**
+     * Sets the focused state.
+     */
+    public void setFocusState(float focusState) {
+        mFocusState = focusState;
+        mStackView.requestSynchronizeStackViewsWithModel();
+    }
+
+    /**
+     * Gets the focused state.
+     */
+    public float getFocusState() {
+        return mFocusState;
+    }
+
+    /**
      * Computes the stack and task rects.  The given task stack bounds is the whole bounds not
      * including the search bar.
      */
     public void initialize(Rect taskStackBounds) {
         SystemServicesProxy ssp = Recents.getSystemServices();
-
+        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
         RecentsConfiguration config = Recents.getConfiguration();
         int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
         int heightPadding = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_stack_top_padding);
+        Rect lastStackRect = new Rect(mCurrentStackRect);
 
         // The freeform height is the visible height (not including system insets) - padding above
         // freeform and below stack - gap between the freeform and stack
@@ -200,43 +290,34 @@
                 taskStackBounds.top + heightPadding,
                 taskStackBounds.right - widthPadding,
                 taskStackBounds.bottom);
+
         // Anchor the task rect to the top-center of the non-freeform stack rect
-        int size = mStackRect.width();
+        float aspect = (float) (taskStackBounds.width() - mSystemInsets.left - mSystemInsets.right)
+                / (taskStackBounds.height() - mSystemInsets.bottom);
+        int width = mStackRect.width();
+        int height = debugFlags.isFullscreenThumbnailsEnabled() ? (int) (width / aspect) : width;
         mTaskRect.set(mStackRect.left, mStackRect.top,
-                mStackRect.left + size, mStackRect.top + size);
+                mStackRect.left + width, mStackRect.top + height);
         mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
 
-        // Compute the progress offsets
-        int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
-                R.dimen.recents_task_bar_height);
-        int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
-        mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
-                mCurrentStackRect);
-        mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
-                mCurrentStackRect);
-        mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
-                mCurrentStackRect);
-        mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
-                mCurrentStackRect);
-        mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mCurrentStackRect);
+        // Short circuit here if the stack rects haven't changed so we don't do all the work below
+        if (lastStackRect.equals(mCurrentStackRect)) {
+            return;
+        }
+
+        // Reinitialize the focused and unfocused curves
+        mUnfocusedCurve = constructUnfocusedCurve();
+        mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
+        mFocusedCurve = constructFocusedCurve();
+        mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
 
         if (DEBUG) {
             Log.d(TAG, "initialize");
-            Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
             Log.d(TAG, "\tmFreeformRect: " + mFreeformRect);
             Log.d(TAG, "\tmFreeformStackRect: " + mFreeformStackRect);
             Log.d(TAG, "\tmStackRect: " + mStackRect);
             Log.d(TAG, "\tmTaskRect: " + mTaskRect);
             Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
-
-            Log.d(TAG, "\tpWithinAffiliateOffset: " + mWithinAffiliationPOffset);
-            Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
-            Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
-            Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
-            Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
-
-            Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mCurrentStackRect));
-            Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mCurrentStackRect));
         }
     }
 
@@ -248,7 +329,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Clear the progress map
-        mTaskProgressMap.clear();
+        mTaskIndexMap.clear();
 
         // Return early if we have no tasks
         ArrayList<Task> tasks = stack.getTasks();
@@ -273,41 +354,28 @@
         mNumStackTasks = stackTasks.size();
         mNumFreeformTasks = freeformTasks.size();
 
-        float pAtBackMostTaskTop = 0;
-        float pAtFrontMostTaskTop = pAtBackMostTaskTop;
-        if (!stackTasks.isEmpty()) {
-            // Update the for each task from back to front.
-            int taskCount = stackTasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                Task task = stackTasks.get(i);
-                mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
+        // 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 = stackTasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = stackTasks.get(i);
+            mTaskIndexMap.put(task.key, i);
+        }
 
-                if (DEBUG) {
-                    Log.d(TAG, "Update: " + task.activityLabel + " p: " + pAtFrontMostTaskTop);
-                }
-
-                if (i < (taskCount - 1)) {
-                    // Increment the peek height
-                    float pPeek = task.group == null || task.group.isFrontMostTask(task) ?
-                            mBetweenAffiliationPOffset : mWithinAffiliationPOffset;
-                    pAtFrontMostTaskTop += pPeek;
-                }
-            }
-
-            mFrontMostTaskP = pAtFrontMostTaskTop;
-            if (mNumStackTasks > 1) {
-                // Set the stack end scroll progress to the point at which the bottom of the front-most
-                // task is aligned to the bottom of the stack
-                mMaxScrollP = alignToStackBottom(pAtFrontMostTaskTop,
-                        mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
-                                mTaskHalfHeightPOffset : mTaskHeightPOffset));
-                // Basically align the back-most task such that the last two tasks would be visible
-                mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
-                        mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
-                                mTaskHalfHeightPOffset : mTaskHeightPOffset));
-            } else {
-                // When there is a single item, then just make all the stack progresses the same
+        // Calculate the min/max scroll
+        if (getDefaultFocusState() > 0f) {
+            mMinScrollP = 0;
+            mMaxScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+        } else {
+            if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
                 mMinScrollP = mMaxScrollP = 0;
+            } else {
+                float bottomOffsetPct = (float) (mStackBottomOffset + mTaskRect.height()) /
+                        mCurrentStackRect.height();
+                float normX = mUnfocusedCurveInterpolator.getX(bottomOffsetPct);
+                mMinScrollP = 0;
+                mMaxScrollP = Math.max(mMinScrollP,
+                        (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX));
             }
         }
 
@@ -315,7 +383,20 @@
             mFreeformLayoutAlgorithm.update(freeformTasks, this);
             mInitialScrollP = mMaxScrollP;
         } else {
-            mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
+            if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+                mInitialScrollP = mMinScrollP;
+            } else if (getDefaultFocusState() > 0f) {
+                RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+                if (launchState.launchedFromHome) {
+                    mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+                } else {
+                    mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 2);
+                }
+            } else {
+                float offsetPct = (float) (mTaskRect.height() / 2) / mCurrentStackRect.height();
+                float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
+                mInitialScrollP = (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX);
+            }
         }
 
         if (DEBUG) {
@@ -327,8 +408,45 @@
     }
 
     /**
-     * Computes the maximum number of visible tasks and thumbnails.  Requires that
-     * update() is called first.
+     * Updates this stack when a scroll happens.
+     */
+    public void updateFocusStateOnScroll(int yMovement) {
+        Utilities.cancelAnimationWithoutCallbacks(mFocusStateAnimator);
+        if (mFocusState > STATE_UNFOCUSED) {
+            float delta = (float) yMovement / (UNFOCUS_MULTIPLIER * mCurrentStackRect.height());
+            mFocusState -= Math.min(mFocusState, Math.abs(delta));
+        }
+    }
+
+    /**
+     * Aniamtes the focused state back to its orginal state.
+     */
+    public void animateFocusState(float newState) {
+        Utilities.cancelAnimationWithoutCallbacks(mFocusStateAnimator);
+        if (Float.compare(newState, getFocusState()) != 0) {
+            mFocusStateAnimator = ObjectAnimator.ofFloat(this, FOCUS_STATE, getFocusState(),
+                    newState);
+            mFocusStateAnimator.setDuration(200);
+            mFocusStateAnimator.setInterpolator(mFastOutSlowInInterpolator);
+            mFocusStateAnimator.start();
+        }
+    }
+
+    /**
+     * Returns the default focus state.
+     */
+    public float getDefaultFocusState() {
+        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+        if (debugFlags.isPageOnToggleEnabled() || launchState.launchedWithAltTab) {
+            return 1f;
+        }
+        return 0f;
+    }
+
+    /**
+     * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
+     * stack scroll.  Requires that update() is called first.
      */
     public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
         // Ensure minimum visibility count
@@ -344,29 +462,32 @@
 
         // Otherwise, walk backwards in the stack and count the number of tasks and visible
         // thumbnails and add that to the total freeform task count
-        int taskHeight = mTaskRect.height();
+        TaskViewTransform tmpTransform = new TaskViewTransform();
+        Range currentRange = getDefaultFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
+        currentRange.offset(mInitialScrollP);
         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_bar_height);
         int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
         int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
-        Task firstNonFreeformTask = tasks.get(tasks.size() - mNumFreeformTasks - 1);
-        float progress = mTaskProgressMap.get(firstNonFreeformTask.key) - mInitialScrollP;
-        int prevScreenY = sCurve.pToX(progress, mCurrentStackRect);
-        for (int i = tasks.size() - 2; i >= 0; i--) {
+        float prevScreenY = Integer.MAX_VALUE;
+        for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
+
+            // Skip freeform
             if (task.isFreeformTask()) {
                 continue;
             }
 
-            progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
-            if (progress < 0) {
-                break;
+            // Skip invisible
+            float taskProgress = getStackScrollForTask(task);
+            if (!currentRange.isInRange(taskProgress)) {
+                continue;
             }
+
             boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
             if (isFrontMostTaskInGroup) {
-                float scaleAtP = sCurve.pToScale(progress);
-                int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
-                int screenY = sCurve.pToX(progress, mCurrentStackRect) + scaleYOffsetAtP;
+                getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null);
+                float screenY = tmpTransform.rect.top;
                 boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
                 if (hasVisibleThumbnail) {
                     numVisibleThumbnails++;
@@ -374,12 +495,12 @@
                     prevScreenY = screenY;
                 } else {
                     // Once we hit the next front most task that does not have a visible thumbnail,
-                    // w  alk through remaining visible set
+                    // walk through remaining visible set
                     for (int j = i; j >= 0; j--) {
                         numVisibleTasks++;
-                        progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
-                        if (progress < 0) {
-                            break;
+                        taskProgress = getStackScrollForTask(tasks.get(j));
+                        if (!currentRange.isInRange(taskProgress)) {
+                            continue;
                         }
                     }
                     break;
@@ -397,86 +518,82 @@
      * is what the view is measured and laid out with.
      */
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+            TaskViewTransform transformOut, TaskViewTransform frontTransform) {
         if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
             mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
             return transformOut;
         } else {
             // Return early if we have an invalid index
-            if (task == null || !mTaskProgressMap.containsKey(task.key)) {
+            if (task == null || !mTaskIndexMap.containsKey(task.key)) {
                 transformOut.reset();
                 return transformOut;
             }
-            return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
-                    prevTransform);
+            return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
+                    frontTransform);
         }
     }
 
     /** Update/get the transform */
     public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+            TaskViewTransform transformOut, TaskViewTransform frontTransform) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
-        if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
-            // Center the task in the stack, changing the scale will not follow the curve, but just
-            // modulate some values directly
-            float pTaskRelative = mMinScrollP - stackScroll;
-            float scale = ssp.hasFreeformWorkspaceSupport() ? 1f : SINGLE_TASK_SCALE;
-            int topOffset = (mCurrentStackRect.top - mTaskRect.top) +
-                    (mCurrentStackRect.height() - mTaskRect.height()) / 2;
-            transformOut.scale = scale;
-            transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
-            transformOut.translationY = (int) (topOffset + (pTaskRelative * mCurrentStackRect.height()));
-            transformOut.translationZ = mMaxTranslationZ;
-            transformOut.rect.set(mTaskRect);
-            transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
-            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-            transformOut.visible = true;
-            transformOut.p = pTaskRelative;
-            return transformOut;
+        // Compute the focused and unfocused offset
+        mUnfocusedRange.offset(stackScroll);
+        float p = mUnfocusedRange.getNormalizedX(taskProgress);
+        float yp = mUnfocusedCurveInterpolator.getInterpolation(p);
+        float unfocusedP = p;
+        int unFocusedY = (int) (Math.max(0f, (1f - yp)) * mCurrentStackRect.height());
+        boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
+        int focusedY = 0;
+        boolean focusedVisible = true;
+        if (mFocusState > 0f) {
+            mFocusedRange.offset(stackScroll);
+            p = mFocusedRange.getNormalizedX(taskProgress);
+            yp = mFocusedCurveInterpolator.getInterpolation(p);
+            focusedY = (int) (Math.max(0f, (1f - yp)) * mCurrentStackRect.height());
+            focusedVisible = mFocusedRange.isInRange(taskProgress);
+        }
 
-        } else {
-            float pTaskRelative = taskProgress - stackScroll;
-            float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
-            if (DEBUG) {
-                Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
-            }
-
-            // If the task top is outside of the bounds below the screen, then immediately reset it
-            if (pTaskRelative > 1f) {
-                transformOut.reset();
-                transformOut.rect.set(mTaskRect);
-                return transformOut;
-            }
-            // The check for the top is trickier, since we want to show the next task if it is at
-            // all visible, even if p < 0.
-            if (pTaskRelative < 0f) {
-                if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
-                    transformOut.reset();
-                    transformOut.rect.set(mTaskRect);
-                    return transformOut;
-                }
-            }
-            float scale = sCurve.pToScale(pBounded);
-            int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
-            transformOut.scale = scale;
-            transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
-            transformOut.translationY = (mCurrentStackRect.top - mTaskRect.top) +
-                    (sCurve.pToX(pBounded, mCurrentStackRect) - mCurrentStackRect.top) -
-                    scaleYOffset;
-            transformOut.translationZ = Math.max(mMinTranslationZ,
-                    mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
-            transformOut.rect.set(mTaskRect);
-            transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
-            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-            transformOut.visible = true;
-            transformOut.p = pTaskRelative;
-            if (DEBUG) {
-                Log.d(TAG, "\t" + transformOut);
-            }
-
+        // Skip if the task is not visible
+        if (!unfocusedVisible && !focusedVisible) {
+            transformOut.reset();
             return transformOut;
         }
+
+        int y;
+        float z;
+        float relP;
+        if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+            // When there is exactly one task, then decouple the task from the stack and just move
+            // in screen space
+            p = (mMinScrollP - stackScroll) / mNumStackTasks;
+            int centerYOffset = (mCurrentStackRect.top - mTaskRect.top) +
+                    (mCurrentStackRect.height() - mTaskRect.height()) / 2;
+            y = (int) (centerYOffset + (p * mCurrentStackRect.height()));
+            z = mMaxTranslationZ;
+            relP = p;
+
+        } else {
+            // Otherwise, update the task to the stack layout
+            y = unFocusedY + (int) (mFocusState * (focusedY - unFocusedY));
+            y += (mCurrentStackRect.top - mTaskRect.top);
+            z = Math.max(mMinTranslationZ, Math.min(mMaxTranslationZ,
+                    mMinTranslationZ + (p * (mMaxTranslationZ - mMinTranslationZ))));
+            relP = unfocusedP;
+        }
+
+        // Fill out the transform
+        transformOut.scale = 1f;
+        transformOut.translationX = (mCurrentStackRect.width() - mTaskRect.width()) / 2;
+        transformOut.translationY = y;
+        transformOut.translationZ = z;
+        transformOut.rect.set(mTaskRect);
+        transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
+        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+        transformOut.visible = true;
+        transformOut.p = relP;
+        return transformOut;
     }
 
     /**
@@ -491,8 +608,8 @@
      * stack.
      */
     float getStackScrollForTask(Task t) {
-        if (!mTaskProgressMap.containsKey(t.key)) return 0f;
-        return mTaskProgressMap.get(t.key);
+        if (!mTaskIndexMap.containsKey(t.key)) return 0f;
+        return mTaskIndexMap.get(t.key);
     }
 
     /**
@@ -501,7 +618,8 @@
      * screen along the arc-length proportionally (1/arclength).
      */
     public float getDeltaPForY(int downY, int y) {
-        float deltaP = (float) (y - downY) / mCurrentStackRect.height() * (1f / sCurve.getArcLength());
+        float deltaP = (float) (y - downY) / mCurrentStackRect.height() *
+                mUnfocusedCurveInterpolator.getArcLength();
         return -deltaP;
     }
 
@@ -510,18 +628,61 @@
      * of the curve, map back to the screen y.
      */
     public int getYForDeltaP(float downScrollP, float p) {
-        int y = (int) ((p - downScrollP) * mCurrentStackRect.height() * sCurve.getArcLength());
+        int y = (int) ((p - downScrollP) * mCurrentStackRect.height() *
+                (1f / mUnfocusedCurveInterpolator.getArcLength()));
         return -y;
     }
 
-    private float alignToStackTop(float p) {
-        // At scroll progress == p, then p is at the top of the stack
+    /**
+     * Creates a new path for the focused curve.
+     */
+    private Path constructFocusedCurve() {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        int taskBarHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.recents_task_bar_height);
+
+        // Initialize the focused curve. This curve is a piecewise curve composed of several
+        // quadradic beziers that goes from (0,1) through (0.5, peek height offset),
+        // (0.667, next task offset), (0.833, bottom task offset), and (1,0).
+        float peekHeightPct = 0f;
+        if (!ssp.hasFreeformWorkspaceSupport()) {
+            peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
+        }
+        Path p = new Path();
+        p.moveTo(0f, 1f);
+        p.lineTo(0.5f, 1f - peekHeightPct);
+        p.lineTo(0.66666667f, (float) (taskBarHeight * 3) / mCurrentStackRect.height());
+        p.lineTo(0.83333333f, (float) (taskBarHeight / 2) / mCurrentStackRect.height());
+        p.lineTo(1f, 0f);
         return p;
     }
 
-    private float alignToStackBottom(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);
+    /**
+     * Creates a new path for the unfocused curve.
+     */
+    private Path constructUnfocusedCurve() {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+
+        // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
+        // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0).  This
+        // ensures that we match the range, at which 0.5 represents the stack scroll at the current
+        // task progress.  Because the height offset can change depending on a resource, we compute
+        // the control point of the second bezier such that between it and a first known point,
+        // there is a tangent at (0.5, peek height offset).
+        float cpoint1X = 0.4f;
+        float cpoint1Y = 1f;
+        float peekHeightPct = 0f;
+        if (!ssp.hasFreeformWorkspaceSupport()) {
+            peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
+        }
+        float slope = ((1f - peekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
+        float b = 1f - slope * cpoint1X;
+        float cpoint2X = 0.75f;
+        float cpoint2Y = slope * cpoint2X + b;
+        Path p = new Path();
+        p.moveTo(0f, 1f);
+        p.cubicTo(0f, 1f, cpoint1X, 1f, 0.5f, 1f - peekHeightPct);
+        p.cubicTo(0.5f, 1f - peekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
+        return p;
     }
 }
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 7250d6a..67710bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -38,10 +38,17 @@
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
+import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -60,7 +67,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -80,14 +86,10 @@
     interface TaskStackViewCallbacks {
         public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
                 boolean lockToTask, Rect bounds, int destinationStack);
-        public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
-        public void onTaskStackFilterTriggered();
-        public void onTaskStackUnfilterTriggered();
     }
 
     TaskStack mStack;
     TaskStackLayoutAlgorithm mLayoutAlgorithm;
-    TaskStackViewFilterAlgorithm mFilterAlgorithm;
     TaskStackViewScroller mStackScroller;
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
@@ -101,6 +103,7 @@
     boolean mStackViewsDirty = true;
     boolean mStackViewsClipDirty = true;
     boolean mAwaitingFirstLayout = true;
+    boolean mEnterAnimationComplete = false;
     boolean mStartEnterAnimationRequestedAfterLayout;
     ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
 
@@ -151,8 +154,7 @@
         setStack(stack);
         mViewPool = new ViewPool<>(context, this);
         mInflater = LayoutInflater.from(context);
-        mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
-        mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
+        mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
         mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
         mStackScroller.setCallbacks(this);
         mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
@@ -290,11 +292,14 @@
         mStackViewsDirty = true;
         mStackViewsClipDirty = true;
         mAwaitingFirstLayout = true;
+        mEnterAnimationComplete = false;
         if (mUIDozeTrigger != null) {
             mUIDozeTrigger.stopDozing();
             mUIDozeTrigger.resetTrigger();
         }
         mStackScroller.reset();
+        mLayoutAlgorithm.reset();
+        requestLayout();
     }
 
     /** Requests that the views be synchronized with the model */
@@ -353,7 +358,7 @@
         }
 
         // Update the stack transforms
-        TaskViewTransform prevTransform = null;
+        TaskViewTransform frontTransform = null;
         for (int i = taskCount - 1; i >= 0; i--) {
             Task task = tasks.get(i);
             if (task.isFreeformTask()) {
@@ -361,7 +366,7 @@
             }
 
             TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
-                    taskTransforms.get(i), prevTransform);
+                    taskTransforms.get(i), frontTransform);
             if (DEBUG) {
                 Log.d(TAG, "updateStackTransform: " + i + ", " + transform.visible);
             }
@@ -386,7 +391,7 @@
                 transform.translationY = Math.min(transform.translationY,
                         mLayoutAlgorithm.mCurrentStackRect.bottom);
             }
-            prevTransform = transform;
+            frontTransform = transform;
         }
         if (visibleRangeOut != null) {
             visibleRangeOut[0] = frontMostVisibleIndex;
@@ -605,15 +610,19 @@
 
     /**
      * Sets the focused task to the provided (bounded taskIndex).
+     *
+     * @return whether or not the stack will scroll as a part of this focus change
      */
-    private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
-        setFocusedTask(taskIndex, scrollToTask, animated, true);
+    private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
+        return setFocusedTask(taskIndex, scrollToTask, animated, true);
     }
 
     /**
      * Sets the focused task to the provided (bounded taskIndex).
+     *
+     * @return whether or not the stack will scroll as a part of this focus change
      */
-    private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
+    private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
                                 final boolean requestViewFocus) {
         // Find the next task to focus
         int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
@@ -629,6 +638,7 @@
             }
         }
 
+        boolean willScroll = false;
         mFocusedTaskIndex = newFocusedTaskIndex;
         if (mFocusedTaskIndex != -1) {
             Runnable focusTaskRunnable = new Runnable() {
@@ -643,19 +653,39 @@
 
             if (scrollToTask) {
                 // TODO: Center the newly focused task view, only if not freeform
-                float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
+                RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+                float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
+                if (!debugFlags.isFullscreenThumbnailsEnabled()) {
+                    newScroll -= 0.5f;
+                }
                 newScroll = mStackScroller.getBoundedStackScroll(newScroll);
-                mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
-                        focusTaskRunnable);
+                if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
+                    mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
+                            focusTaskRunnable);
+                    willScroll = true;
+
+                    // Cancel any running enter animations at this point when we scroll as well
+                    if (!mEnterAnimationComplete) {
+                        final List<TaskView> taskViews = getTaskViews();
+                        for (TaskView tv : taskViews) {
+                            tv.cancelEnterRecentsAnimation();
+                        }
+                    }
+                } else {
+                    focusTaskRunnable.run();
+                }
+                mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
             } else {
                 focusTaskRunnable.run();
             }
         }
+        return willScroll;
     }
 
     /**
      * Sets the focused task relative to the currently focused task.
      *
+     * @param forward whether to go to the next task in the stack (along the curve) or the previous
      * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
      *                       if the currently focused task is not a stack task, will set the focus
      *                       to the first visible stack task
@@ -663,6 +693,23 @@
      *                            focus.
      */
     public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) {
+        setRelativeFocusedTask(forward, stackTasksOnly, animated, false);
+    }
+
+    /**
+     * Sets the focused task relative to the currently focused task.
+     *
+     * @param forward whether to go to the next task in the stack (along the curve) or the previous
+     * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
+     *                       if the currently focused task is not a stack task, will set the focus
+     *                       to the first visible stack task
+     * @param animated determines whether to actually draw the highlight along with the change in
+     *                            focus.
+     * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
+     *                               happens
+     */
+    public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
+                                       boolean cancelWindowAnimations) {
         int newIndex = -1;
         if (mFocusedTaskIndex != -1) {
             if (stackTasksOnly) {
@@ -686,8 +733,10 @@
                     }
                 }
             } else {
-                // No restrictions, lets just move to the new task
-                newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+                // No restrictions, lets just move to the new task (looping forward/backwards if
+                // necessary)
+                int taskCount = mStack.getTaskCount();
+                newIndex = (mFocusedTaskIndex + (forward ? -1 : 1) + taskCount) % taskCount;
             }
         } else {
             // We don't have a focused task, so focus the first visible task view
@@ -697,7 +746,12 @@
             }
         }
         if (newIndex != -1) {
-            setFocusedTask(newIndex, true, animated);
+            boolean willScroll = setFocusedTask(newIndex, true, animated);
+            if (willScroll && cancelWindowAnimations) {
+                // As we iterate to the next/previous task, cancel any current/lagging window
+                // transition animations
+                EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
+            }
         }
     }
 
@@ -933,9 +987,15 @@
         for (int i = taskViewCount - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            boolean occludesLaunchTarget = (launchTargetTask != null) &&
-                    launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
-            tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget,
+            boolean hideTask = false;
+            boolean occludesLaunchTarget = false;
+            if (launchTargetTask != null) {
+                occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task,
+                        launchTargetTask);
+                hideTask = SystemServicesProxy.isFreeformStack(launchTargetTask.key.stackId) &&
+                        SystemServicesProxy.isFreeformStack(task.key.stackId);
+            }
+            tv.prepareEnterRecentsAnimation(task.isLaunchTarget, hideTask, occludesLaunchTarget,
                     offscreenY);
         }
 
@@ -1030,20 +1090,6 @@
         }
     }
 
-    /** Requests this task stack to start it's dismiss-all animation. */
-    public void startDismissAllAnimation(final Runnable postAnimationRunnable) {
-        // Clear the focused task
-        resetFocusedTask();
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        int count = 0;
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
-            count++;
-        }
-    }
-
     /** Animates a task view in this stack as it launches. */
     public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
         Task launchTargetTask = tv.getTask();
@@ -1071,8 +1117,11 @@
         mLayersDisabled = false;
 
         // Draw the freeform workspace background
-        if (mFreeformWorkspaceBackground.getAlpha() > 0) {
-            mFreeformWorkspaceBackground.draw(canvas);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.hasFreeformWorkspaceSupport()) {
+            if (mFreeformWorkspaceBackground.getAlpha() > 0) {
+                mFreeformWorkspaceBackground.draw(canvas);
+            }
         }
 
         super.dispatchDraw(canvas);
@@ -1116,8 +1165,8 @@
                 mViewPool.returnViewToPool(tv);
             }
 
-            // Get the stack scroll of the task to anchor to (since we are removing something, the front
-            // most task will be our anchor task)
+            // Get the stack scroll of the task to anchor to (since we are removing something, the
+            // front most task will be our anchor task)
             Task anchorTask = null;
             float prevAnchorTaskScroll = 0;
             boolean pullStackForward = stack.getTaskCount() > 0;
@@ -1134,11 +1183,16 @@
                 // to ensure that the new front most task is now fully visible
                 mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
             } else if (pullStackForward) {
-                // Otherwise, offset the scroll by half the movement of the anchor task to allow the
-                // tasks behind the removed task to move forward, and the tasks in front to move back
+                // Otherwise, offset the scroll by the movement of the anchor task
                 float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
-                mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
-                        - prevAnchorTaskScroll) / 2);
+                float newStackScroll = mStackScroller.getStackScroll() +
+                        (anchorTaskScroll - prevAnchorTaskScroll);
+                if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
+                    // If we are focused, we don't want the front task to move, but otherwise, we
+                    // allow the back task to move up, and the front task to move back
+                    newStackScroll /= 2;
+                }
+                mStackScroller.setStackScroll(newStackScroll);
                 mStackScroller.boundScroll();
             }
 
@@ -1169,99 +1223,15 @@
             }
         }
 
-        // If there are no remaining tasks, then either unfilter the current stack, or just close
-        // the activity if there are no filtered stacks
+        // If there are no remaining tasks, then just close recents
         if (mStack.getTaskCount() == 0) {
-            boolean shouldFinishActivity = true;
-            if (mStack.hasFilteredTasks()) {
-                mStack.unfilterTasks();
-                shouldFinishActivity = (mStack.getTaskCount() == 0);
-            }
+            boolean shouldFinishActivity = (mStack.getTaskCount() == 0);
             if (shouldFinishActivity) {
-                mCb.onAllTaskViewsDismissed(null);
+                EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
             }
         }
     }
 
-    @Override
-    public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) {
-        // Announce for accessibility
-        String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed);
-        announceForAccessibility(msg);
-
-        startDismissAllAnimation(new Runnable() {
-            @Override
-            public void run() {
-                // Notify that all tasks have been removed
-                mCb.onAllTaskViewsDismissed(removedTasks);
-            }
-        });
-    }
-
-    @Override
-    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
-                                Task filteredTask) {
-        /*
-        // Stash the scroll and filtered task for us to restore to when we unfilter
-        mStashedScroll = getStackScroll();
-
-        // Calculate the current task transforms
-        ArrayList<TaskViewTransform> curTaskTransforms =
-                getStackTransforms(curTasks, getStackScroll(), null, true);
-
-        // Update the task offsets
-        mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
-
-        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
-        updateLayout(false);
-        float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight();
-        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
-        boundScrollRaw();
-
-        // Compute the transforms of the items in the new stack after setting the new scroll
-        final ArrayList<Task> tasks = mStack.getTasks();
-        final ArrayList<TaskViewTransform> taskTransforms =
-                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
-
-        // Animate
-        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
-
-        // Notify any callbacks
-        mCb.onTaskStackFilterTriggered();
-        */
-    }
-
-    @Override
-    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
-        /*
-        // Calculate the current task transforms
-        final ArrayList<TaskViewTransform> curTaskTransforms =
-                getStackTransforms(curTasks, getStackScroll(), null, true);
-
-        // Update the task offsets
-        mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
-
-        // Restore the stashed scroll
-        updateLayout(false);
-        setStackScrollRaw(mStashedScroll);
-        boundScrollRaw();
-
-        // Compute the transforms of the items in the new stack after restoring the stashed scroll
-        final ArrayList<Task> tasks = mStack.getTasks();
-        final ArrayList<TaskViewTransform> taskTransforms =
-                getStackTransforms(tasks, getStackScroll(), null, true);
-
-        // Animate
-        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
-
-        // Clear the saved vars
-        mStashedScroll = 0;
-
-        // Notify any callbacks
-        mCb.onTaskStackUnfilterTriggered();
-        */
-    }
-
     /**** ViewPoolConsumer Implementation ****/
 
     @Override
@@ -1502,6 +1472,30 @@
         requestSynchronizeStackViewsWithModel(175);
     }
 
+    public final void onBusEvent(StackViewScrolledEvent event) {
+        mLayoutAlgorithm.updateFocusStateOnScroll(event.yMovement);
+    }
+
+    public final void onBusEvent(IterateRecentsEvent event) {
+        mLayoutAlgorithm.animateFocusState(mLayoutAlgorithm.getDefaultFocusState());
+    }
+
+    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
+        mEnterAnimationComplete = true;
+    }
+
+    public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
+        List<TaskView> taskViews = getTaskViews();
+        int taskViewCount = taskViews.size();
+        for (int i = 0; i < taskViewCount; i++) {
+            TaskView tv = taskViews.get(i);
+            Task task = tv.getTask();
+            if (SystemServicesProxy.isFreeformStack(task.key.stackId)) {
+                tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
+            }
+        }
+    }
+
     /**
      * Removes the task from the stack, and updates the focus to the next task in the stack if the
      * removed TaskView was focused.
@@ -1521,16 +1515,5 @@
 
         // 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();
-            boolean isFreeformTask = taskIndex > 0 ?
-                    mStack.getTasks().get(taskIndex - 1).isFreeformTask() : false;
-            setFocusedTask(taskIndex - 1, !isFreeformTask /* scrollToTask */,
-                    launchState.launchedWithAltTab);
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
deleted file mode 100644
index 45f573d..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/* The layout logic for a TaskStackView */
-public class TaskStackViewFilterAlgorithm {
-
-    TaskStackView mStackView;
-    ViewPool<TaskView, Task> mViewPool;
-
-    public TaskStackViewFilterAlgorithm(TaskStackView stackView, ViewPool<TaskView, Task> viewPool) {
-        mStackView = stackView;
-        mViewPool = viewPool;
-    }
-
-    /** Orchestrates the animations of the current child views and any new views. */
-    void startFilteringAnimation(ArrayList<Task> curTasks,
-                                 ArrayList<TaskViewTransform> curTaskTransforms,
-                                 final ArrayList<Task> tasks,
-                                 final ArrayList<TaskViewTransform> taskTransforms) {
-        // Calculate the transforms to animate out all the existing views if they are not in the
-        // new visible range (or to their final positions in the stack if they are)
-        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
-        final HashMap<TaskView, TaskViewTransform> childViewTransforms =
-                new HashMap<TaskView, TaskViewTransform>();
-        int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
-                taskTransforms, childViewTransforms, childrenToRemove);
-
-        // If all the current views are in the visible range of the new stack, then don't wait for
-        // views to animate out and animate all the new views into their place
-        final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
-        if (unifyNewViewAnimation) {
-            int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
-                    childViewTransforms);
-            duration = Math.max(duration, inDuration);
-        }
-
-        // Animate all the views to their final transforms
-        for (final TaskView tv : childViewTransforms.keySet()) {
-            TaskViewTransform t = childViewTransforms.get(tv);
-            tv.animate().cancel();
-            tv.animate()
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            childViewTransforms.remove(tv);
-                            if (childViewTransforms.isEmpty()) {
-                                // Return all the removed children to the view pool
-                                for (TaskView tv : childrenToRemove) {
-                                    mViewPool.returnViewToPool(tv);
-                                }
-
-                                if (!unifyNewViewAnimation) {
-                                    // For views that are not already visible, animate them in
-                                    childViewTransforms.clear();
-                                    int duration = getEnterTransformsForFilterAnimation(tasks,
-                                            taskTransforms, childViewTransforms);
-                                    for (final TaskView tv : childViewTransforms.keySet()) {
-                                        TaskViewTransform t = childViewTransforms.get(tv);
-                                        tv.updateViewPropertiesToTaskTransform(t, duration);
-                                    }
-                                }
-                            }
-                        }
-                    });
-            tv.updateViewPropertiesToTaskTransform(t, duration);
-        }
-    }
-
-    /**
-     * Creates the animations for all the children views that need to be animated in when we are
-     * un/filtering a stack, and returns the duration for these animations.
-     */
-    int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
-                                             ArrayList<TaskViewTransform> taskTransforms,
-                                             HashMap<TaskView, TaskViewTransform> childViewTransformsOut) {
-        int offset = 0;
-        int movement = 0;
-        int taskCount = tasks.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-            TaskViewTransform toTransform = taskTransforms.get(i);
-            if (toTransform.visible) {
-                TaskView tv = mStackView.getChildViewForTask(task);
-                if (tv == null) {
-                    // For views that are not already visible, animate them in
-                    tv = mViewPool.pickUpViewFromPool(task, task);
-
-                    // Compose a new transform to fade and slide the new task in
-                    TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
-                    tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
-                    tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
-
-                    toTransform.startDelay = offset * 25;
-                    childViewTransformsOut.put(tv, toTransform);
-
-                    // Use the movement of the new views to calculate the duration of the animation
-                    movement = Math.max(movement,
-                            Math.abs(toTransform.translationY - fromTransform.translationY));
-                    offset++;
-                }
-            }
-        }
-        return mStackView.getResources().getInteger(
-                R.integer.recents_filter_animate_new_views_duration);
-    }
-
-    /**
-     * Creates the animations for all the children views that need to be removed or to move views
-     * to their un/filtered position when we are un/filtering a stack, and returns the duration
-     * for these animations.
-     */
-    int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
-                                            ArrayList<TaskViewTransform> curTaskTransforms,
-                                            ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
-                                            HashMap<TaskView, TaskViewTransform> childViewTransformsOut,
-                                            ArrayList<TaskView> childrenToRemoveOut) {
-        // Animate all of the existing views out of view (if they are not in the visible range in
-        // the new stack) or to their final positions in the new stack
-        int offset = 0;
-        int movement = 0;
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-            int taskIndex = tasks.indexOf(task);
-            TaskViewTransform toTransform;
-
-            // If the view is no longer visible, then we should just animate it out
-            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
-            if (willBeInvisible) {
-                if (taskIndex < 0) {
-                    toTransform = curTaskTransforms.get(curTasks.indexOf(task));
-                } else {
-                    toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
-                }
-                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
-                childrenToRemoveOut.add(tv);
-            } else {
-                toTransform = taskTransforms.get(taskIndex);
-                // Use the movement of the visible views to calculate the duration of the animation
-                movement = Math.max(movement, Math.abs(toTransform.translationY -
-                        (int) tv.getTranslationY()));
-            }
-
-            toTransform.startDelay = offset * 25;
-            childViewTransformsOut.put(tv, toTransform);
-            offset++;
-        }
-        return mStackView.getResources().getInteger(
-                R.integer.recents_filter_animate_current_views_duration);
-    }
-
-}
\ No newline at end of file
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 3a2ed0f..62b640e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -166,12 +166,6 @@
         mScrollAnimator.setDuration(mContext.getResources().getInteger(
                 R.integer.recents_animate_task_stack_scroll_duration));
         mScrollAnimator.setInterpolator(mLinearOutSlowInInterpolator);
-        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                setStackScroll((Float) animation.getAnimatedValue());
-            }
-        });
         mScrollAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
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 81c89a1..907ed2f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -31,10 +31,10 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -59,6 +59,7 @@
     boolean mIsScrolling;
     float mDownScrollP;
     int mDownX, mDownY;
+    int mLastY;
     int mActivePointerId = INACTIVE_POINTER_ID;
     int mOverscrollSize;
     TaskView mActiveTaskView = null;
@@ -150,11 +151,6 @@
         if (mSv.getTaskViews().size() == 0) {
             return false;
         }
-        // Short circuit while we are alt-tabbing
-        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-        if (launchState.launchedWithAltTab) {
-            return false;
-        }
 
         final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
         int action = ev.getAction();
@@ -163,6 +159,7 @@
                 // Save the touch down info
                 mDownX = (int) ev.getX();
                 mDownY = (int) ev.getY();
+                mLastY = mDownY;
                 mDownScrollP = mScroller.getStackScroll();
                 mActivePointerId = ev.getPointerId(0);
                 mActiveTaskView = findViewAtPoint(mDownX, mDownY);
@@ -181,6 +178,7 @@
                 final int index = ev.getActionIndex();
                 mDownX = (int) ev.getX();
                 mDownY = (int) ev.getY();
+                mLastY = mDownY;
                 mDownScrollP = mScroller.getStackScroll();
                 mActivePointerId = ev.getPointerId(index);
                 mVelocityTracker.addMovement(ev);
@@ -209,8 +207,10 @@
                     if (DEBUG) {
                         Log.d(TAG, "scroll: " + curScrollP);
                     }
+                    EventBus.getDefault().send(new StackViewScrolledEvent(y - mLastY));
                 }
 
+                mLastY = y;
                 mVelocityTracker.addMovement(ev);
                 break;
             }
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 2c8f316..523f84f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -17,6 +17,7 @@
 package com.android.systemui.recents.views;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -29,7 +30,6 @@
 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;
@@ -40,12 +40,10 @@
 import android.view.animation.Interpolator;
 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.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -155,7 +153,7 @@
     }
 
     /** Gets the task */
-    Task getTask() {
+    public Task getTask() {
         return mTask;
     }
 
@@ -200,7 +198,7 @@
 
         // Measure the content
         mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
+                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
 
         // Measure the bar view, and action button
         mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
@@ -211,7 +209,7 @@
         // Measure the thumbnail to be square
         mThumbnailView.measure(
                 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
+                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
         mThumbnailView.updateClipToTaskBar(mHeaderView);
         setMeasuredDimension(width, height);
         invalidateOutline();
@@ -253,6 +251,7 @@
             mActionButtonView.setAlpha(1f);
             mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
         }
+        setVisibility(View.VISIBLE);
     }
 
     /**
@@ -277,12 +276,14 @@
 
     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
      * first layout because the actual animation into recents may take a long time. */
-    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
-                                             boolean occludesLaunchTarget, int offscreenY) {
+    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask,
+            boolean occludesLaunchTarget, int offscreenY) {
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         int initialDim = getDim();
-        if (launchState.launchedHasConfigurationChanged) {
+        if (hideTask) {
+            setVisibility(View.INVISIBLE);
+        } else if (launchState.launchedHasConfigurationChanged) {
             // Just load the views as-is
         } else if (launchState.launchedFromAppWithThumbnail) {
             if (isTaskViewLaunchTargetTask) {
@@ -340,15 +341,21 @@
                     animate().alpha(1f)
                             .translationY(transform.translationY)
                             .setUpdateListener(null)
-                            .setInterpolator(mFastOutSlowInInterpolator)
-                            .setDuration(taskViewEnterFromHomeDuration)
-                            .withEndAction(new Runnable() {
+                            .setListener(new AnimatorListenerAdapter() {
+                                private boolean hasEnded;
+
+                                // We use the animation listener instead of withEndAction() to
+                                // ensure that onAnimationEnd() is called when the animator is
+                                // cancelled
                                 @Override
-                                public void run() {
-                                    // Decrement the post animation trigger
+                                public void onAnimationEnd(Animator animation) {
+                                    if (hasEnded) return;
                                     ctx.postAnimationTrigger.decrement();
+                                    hasEnded = true;
                                 }
                             })
+                            .setInterpolator(mFastOutSlowInInterpolator)
+                            .setDuration(taskViewEnterFromHomeDuration)
                             .start();
                     ctx.postAnimationTrigger.increment();
                 }
@@ -368,21 +375,30 @@
                     .translationY(transform.translationY)
                     .setStartDelay(delay)
                     .setUpdateListener(ctx.updateListener)
+                    .setListener(new AnimatorListenerAdapter() {
+                        private boolean hasEnded;
+
+                        // We use the animation listener instead of withEndAction() to ensure that
+                        // onAnimationEnd() is called when the animator is cancelled
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            if (hasEnded) return;
+                            ctx.postAnimationTrigger.decrement();
+                            hasEnded = true;
+                        }
+                    })
                     .setInterpolator(mQuintOutInterpolator)
                     .setDuration(taskViewEnterFromHomeDuration +
                             frontIndex * taskViewEnterFromHomeStaggerDelay)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            // Decrement the post animation trigger
-                            ctx.postAnimationTrigger.decrement();
-                        }
-                    })
                     .start();
             ctx.postAnimationTrigger.increment();
         }
     }
 
+    public void cancelEnterRecentsAnimation() {
+        animate().cancel();
+    }
+
     public void fadeInActionButton(int duration) {
         // Hide the action button
         mActionButtonView.setAlpha(0f);
@@ -409,11 +425,6 @@
         ctx.postAnimationTrigger.increment();
     }
 
-    /** Animates this task view away when dismissing all tasks. */
-    void startDismissAllAnimation() {
-        dismissTask();
-    }
-
     /** Animates this task view as it exits recents */
     void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
             boolean occludesLaunchTarget, boolean lockToTask) {
@@ -619,23 +630,6 @@
         setDim(getDimFromTaskProgress());
     }
 
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        super.dispatchDraw(canvas);
-
-        RecentsDebugFlags flags = Recents.getDebugFlags();
-        if (flags.isFastToggleRecentsEnabled() && mIsFocused) {
-            Paint tmpPaint = new Paint();
-            Rect tmpRect = new Rect();
-            tmpRect.set(0, 0, getWidth(), getHeight());
-            tmpPaint.setColor(0xFFFF0000);
-            tmpPaint.setStrokeWidth(35);
-            tmpPaint.setStyle(Paint.Style.STROKE);
-            canvas.drawRect(tmpRect, tmpPaint);
-        }
-    }
-
     /**** View focus state ****/
 
     /**
@@ -666,10 +660,6 @@
                 clearAccessibilityFocus();
             }
         }
-        RecentsDebugFlags flags = Recents.getDebugFlags();
-        if (flags.isFastToggleRecentsEnabled()) {
-            invalidate();
-        }
     }
 
     /**
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 649199e..76c6691 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,12 +16,7 @@
 
 package com.android.systemui.recents.views;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
@@ -69,16 +64,13 @@
     TextView mActivityDescription;
 
     // Header drawables
-    boolean mCurrentPrimaryColorIsDark;
-    int mCurrentPrimaryColor;
-    int mBackgroundColor;
     int mCornerRadius;
     int mHighlightHeight;
     Drawable mLightDismissDrawable;
     Drawable mDarkDismissDrawable;
     RippleDrawable mBackground;
     GradientDrawable mBackgroundColorDrawable;
-    AnimatorSet mFocusAnimator;
+    ObjectAnimator mFocusAnimator;
     String mDismissContentDescription;
 
     // Static highlight that we draw at the top of each view
@@ -223,15 +215,12 @@
                 ((ColorDrawable) getBackground()).getColor() : 0;
         if (existingBgColor != t.colorPrimary) {
             mBackgroundColorDrawable.setColor(t.colorPrimary);
-            mBackgroundColor = t.colorPrimary;
         }
 
         int taskBarViewLightTextColor = getResources().getColor(
                 R.color.recents_task_bar_light_text_color);
         int taskBarViewDarkTextColor = getResources().getColor(
                 R.color.recents_task_bar_dark_text_color);
-        mCurrentPrimaryColor = t.colorPrimary;
-        mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
         mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
                 taskBarViewLightTextColor : taskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
@@ -258,7 +247,6 @@
 
         // Stop any focus animations
         Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
-        mBackground.jumpToCurrentState();
     }
 
     /** Updates the resize task bar button. */
@@ -376,84 +364,22 @@
             isRunning = mFocusAnimator.isRunning();
         }
         Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
-        mBackground.jumpToCurrentState();
 
         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[][] {
-                    new int[] {},
-                    new int[] { android.R.attr.state_enabled },
-                    new int[] { android.R.attr.state_pressed }
-            };
-            int[] newStates = new int[]{
-                    0,
-                    android.R.attr.state_enabled,
-                    android.R.attr.state_pressed
-            };
-            int[] colors = new int[] {
-                    currentColor,
-                    secondaryColor,
-                    secondaryColor
-            };
-            mBackground.setColor(new ColorStateList(states, colors));
-            mBackground.setState(newStates);
-            // Pulse the background color
-            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
-            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
-                    currentColor, lightPrimaryColor);
-            backgroundColor.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    mBackground.setState(new int[]{});
-                }
-            });
-            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    int color = (int) animation.getAnimatedValue();
-                    mBackgroundColorDrawable.setColor(color);
-                    mBackgroundColor = color;
-                }
-            });
-            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
-            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
-            // Pulse the translation
-            ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
-            translation.setRepeatCount(ValueAnimator.INFINITE);
-            translation.setRepeatMode(ValueAnimator.REVERSE);
-
-            mFocusAnimator = new AnimatorSet();
-            mFocusAnimator.playTogether(backgroundColor, translation);
-            mFocusAnimator.setStartDelay(150);
-            mFocusAnimator.setDuration(750);
+            // Bump up the translation
+            mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 8f);
+            mFocusAnimator.setDuration(200);
             mFocusAnimator.start();
         } else {
             if (isRunning) {
-                // Restore the background color
-                int currentColor = mBackgroundColor;
-                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
-                        currentColor, mCurrentPrimaryColor);
-                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        int color = (int) animation.getAnimatedValue();
-                        mBackgroundColorDrawable.setColor(color);
-                        mBackgroundColor = color;
-                    }
-                });
                 // Restore the translation
-                ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
-
-                mFocusAnimator = new AnimatorSet();
-                mFocusAnimator.playTogether(backgroundColor, translation);
+                mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 0f);
                 mFocusAnimator.setDuration(150);
                 mFocusAnimator.start();
             } else {
-                mBackground.setState(new int[] {});
                 setTranslationZ(0f);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index f8ab35f..723989a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -80,6 +80,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
 import android.widget.DateTimeView;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
 import android.widget.TextView;
@@ -280,6 +281,10 @@
         @Override
         public boolean onClickHandler(
                 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+            if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+                return true;
+            }
+
             if (DEBUG) {
                 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
             }
@@ -368,6 +373,65 @@
                 Intent fillInIntent) {
             return super.onClickHandler(view, pendingIntent, fillInIntent);
         }
+
+        private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+            Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
+            RemoteInput[] inputs = null;
+            if (tag instanceof RemoteInput[]) {
+                inputs = (RemoteInput[]) tag;
+            }
+
+            if (inputs == null) {
+                return false;
+            }
+
+            RemoteInput input = null;
+
+            for (RemoteInput i : inputs) {
+                if (i.getAllowFreeFormInput()) {
+                    input = i;
+                }
+            }
+
+            if (input == null) {
+                return false;
+            }
+
+            ViewParent p = view.getParent();
+            RemoteInputView riv = null;
+            while (p != null) {
+                if (p instanceof View) {
+                    View pv = (View) p;
+                    if (pv.isRootNamespace()) {
+                        riv = (RemoteInputView) pv.findViewWithTag(RemoteInputView.VIEW_TAG);
+                        break;
+                    }
+                }
+                p = p.getParent();
+            }
+
+            if (riv == null) {
+                return false;
+            }
+
+            riv.setVisibility(View.VISIBLE);
+            int cx = view.getLeft() + view.getWidth() / 2;
+            int cy = view.getTop() + view.getHeight() / 2;
+            int w = riv.getWidth();
+            int h = riv.getHeight();
+            int r = Math.max(
+                    Math.max(cx + cy, cx + (h - cy)),
+                    Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+            ViewAnimationUtils.createCircularReveal(riv, cx, cy, 0, r)
+                    .start();
+
+            riv.setPendingIntent(pendingIntent);
+            riv.setRemoteInput(inputs, input);
+            riv.focus();
+
+            return true;
+        }
+
     };
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1551,15 +1615,15 @@
 
         RemoteInput remoteInput = null;
 
-        // See if the notification has exactly one action and this action allows free-form input
-        // TODO: relax restrictions once we support more than one remote input action.
         Notification.Action[] actions = entry.notification.getNotification().actions;
-        if (actions != null && actions.length == 1) {
-            if (actions[0].getRemoteInputs() != null) {
-                for (RemoteInput ri : actions[0].getRemoteInputs()) {
-                    if (ri.getAllowFreeFormInput()) {
-                        remoteInput = ri;
-                        break;
+        if (actions != null) {
+            for (Notification.Action a : actions) {
+                if (a.getRemoteInputs() != null) {
+                    for (RemoteInput ri : a.getRemoteInputs()) {
+                        if (ri.getAllowFreeFormInput()) {
+                            remoteInput = ri;
+                            break;
+                        }
                     }
                 }
             }
@@ -1569,32 +1633,36 @@
         if (remoteInput != null) {
             View bigContentView = entry.getExpandedContentView();
             if (bigContentView != null) {
-                inflateRemoteInput(bigContentView, entry, remoteInput, actions);
+                inflateRemoteInput(bigContentView, entry);
             }
             View headsUpContentView = entry.getHeadsUpContentView();
             if (headsUpContentView != null) {
-                inflateRemoteInput(headsUpContentView, entry, remoteInput, actions);
+                inflateRemoteInput(headsUpContentView, entry);
             }
         }
 
     }
 
-    private void inflateRemoteInput(View view, Entry entry, RemoteInput remoteInput,
-            Notification.Action[] actions) {
-        View actionContainerCandidate = view.findViewById(com.android.internal.R.id.actions);
-        if (actionContainerCandidate instanceof ViewGroup) {
-            ViewGroup actionContainer = (ViewGroup) actionContainerCandidate;
-            RemoteInputView riv = inflateRemoteInputView(actionContainer, entry,
-                    actions[0], remoteInput);
+    private RemoteInputView inflateRemoteInput(View view, Entry entry) {
+        View actionContainerCandidate = view.findViewById(
+                com.android.internal.R.id.actions_container);
+        if (actionContainerCandidate instanceof FrameLayout) {
+            ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
+            RemoteInputView riv = inflateRemoteInputView(actionContainer, entry);
             if (riv != null) {
-                actionContainer.removeAllViews();
-                actionContainer.addView(riv);
+                riv.setVisibility(View.INVISIBLE);
+                actionContainer.addView(riv, new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.MATCH_PARENT)
+                );
+                riv.setBackgroundColor(entry.notification.getNotification().color);
+                return riv;
             }
         }
+        return null;
     }
 
-    protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
-            Notification.Action action, RemoteInput remoteInput) {
+    protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry) {
         return null;
     }
 
@@ -2024,7 +2092,7 @@
         Notification n = notification.getNotification();
         mNotificationData.updateRanking(ranking);
 
-        boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
+        boolean applyInPlace = entry.cacheContentViews(mContext, notification.getNotification());
         boolean shouldInterrupt = shouldInterrupt(entry, notification);
         boolean alertAgain = alertAgain(entry, n);
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 0466c14..742be02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -197,8 +197,8 @@
                                 ? (int) event.getRawY()
                                 : (int) event.getRawX(),
                         !mIsVertical
-                                ? mVelocityTracker.getXVelocity()
-                                : mVelocityTracker.getYVelocity());
+                                ? mVelocityTracker.getYVelocity()
+                                : mVelocityTracker.getXVelocity());
             } else if (mDragMode == DRAG_MODE_RECENTS) {
                 mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
             }
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 781d651..0f182ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1102,10 +1102,8 @@
     }
 
     @Override
-    protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
-            Notification.Action action, RemoteInput remoteInput) {
-        return RemoteInputView.inflate(mContext, root, entry, action, remoteInput,
-                mRemoteInputController);
+    protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry) {
+        return RemoteInputView.inflate(mContext, root, entry, mRemoteInputController);
     }
 
     public int getStatusBarHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 2ad9287..acfe54d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -34,11 +34,13 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
-import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -47,16 +49,21 @@
 /**
  * Host for the remote input.
  */
-public class RemoteInputView extends FrameLayout implements View.OnClickListener {
+public class RemoteInputView extends LinearLayout implements View.OnClickListener {
 
     private static final String TAG = "RemoteInput";
 
+    // A marker object that let's us easily find views of this class.
+    public static final Object VIEW_TAG = new Object();
+
     private RemoteEditText mEditText;
+    private ImageButton mSendButton;
     private ProgressBar mProgressBar;
     private PendingIntent mPendingIntent;
+    private RemoteInput[] mRemoteInputs;
     private RemoteInput mRemoteInput;
-    private Notification.Action mAction;
     private RemoteInputController mController;
+
     private NotificationData.Entry mEntry;
 
     public RemoteInputView(Context context, AttributeSet attrs) {
@@ -69,6 +76,9 @@
 
         mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
 
+        mSendButton = (ImageButton) findViewById(R.id.remote_input_send);
+        mSendButton.setOnClickListener(this);
+
         mEditText = (RemoteEditText) getChildAt(0);
         mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
             @Override
@@ -99,10 +109,11 @@
         Bundle results = new Bundle();
         results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
         Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
+        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
                 results);
 
         mEditText.setEnabled(false);
+        mSendButton.setVisibility(INVISIBLE);
         mProgressBar.setVisibility(VISIBLE);
 
         try {
@@ -113,17 +124,13 @@
     }
 
     public static RemoteInputView inflate(Context context, ViewGroup root,
-            NotificationData.Entry entry, Notification.Action action, RemoteInput remoteInput,
+            NotificationData.Entry entry,
             RemoteInputController controller) {
         RemoteInputView v = (RemoteInputView)
                 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
-
-        v.mEditText.setHint(action.title);
-        v.mPendingIntent = action.actionIntent;
-        v.mRemoteInput = remoteInput;
-        v.mAction = action;
         v.mController = controller;
         v.mEntry = entry;
+        v.setTag(VIEW_TAG);
 
         return v;
     }
@@ -132,15 +139,16 @@
     public void onClick(View v) {
         if (v == mEditText) {
             if (!mEditText.isFocusable()) {
-                mEditText.setInnerFocusable(true);
-                mController.addRemoteInput(mEntry);
-                mEditText.mShowImeOnInputConnection = true;
+                focus();
             }
+        } else if (v == mSendButton) {
+            sendRemoteInput();
         }
     }
 
     public void onDefocus() {
         mController.removeRemoteInput(mEntry);
+        setVisibility(INVISIBLE);
     }
 
     @Override
@@ -149,6 +157,23 @@
         mController.removeRemoteInput(mEntry);
     }
 
+    public void setPendingIntent(PendingIntent pendingIntent) {
+        mPendingIntent = pendingIntent;
+    }
+
+    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+        mRemoteInputs = remoteInputs;
+        mRemoteInput = remoteInput;
+        mEditText.setHint(mRemoteInput.getLabel());
+    }
+
+    public void focus() {
+        mEditText.setInnerFocusable(true);
+        mController.addRemoteInput(mEntry);
+        mEditText.mShowImeOnInputConnection = true;
+        mEditText.requestFocus();
+    }
+
     /**
      * An EditText that changes appearance based on whether it's focusable and becomes
      * un-focusable whenever the user navigates away from it or it becomes invisible.
@@ -220,6 +245,13 @@
             return inputConnection;
         }
 
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
         void setInnerFocusable(boolean focusable) {
             setFocusableInTouchMode(focusable);
             setFocusable(focusable);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index c5ce2fd..f6af942 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2251,7 +2251,7 @@
         public boolean matches(PendingIntent pi, IAlarmListener rec) {
             return (operation != null)
                     ? operation.equals(pi)
-                    : listener.asBinder().equals(rec.asBinder());
+                    : rec != null && listener.asBinder().equals(rec.asBinder());
         }
 
         public boolean matches(String packageName) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 29173a5..94a908c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1274,6 +1274,7 @@
     boolean mAlwaysFinishActivities = false;
     boolean mForceResizableActivities;
     boolean mSupportsFreeformWindowManagement;
+    boolean mTakeFullscreenScreenshots;
     IActivityController mController = null;
     String mProfileApp = null;
     ProcessRecord mProfileProc = null;
@@ -1646,7 +1647,7 @@
                 break;
             }
             case START_USER_SWITCH_UI_MSG: {
-                mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
+                mUserController.showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
                 break;
             }
             case DISMISS_DIALOG_UI_MSG: {
@@ -4320,7 +4321,8 @@
                 }
                 if (task.stack.mStackId != launchStackId) {
                     mStackSupervisor.moveTaskToStackLocked(
-                            taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
+                            taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
+                            true /* animate */);
                 }
             }
 
@@ -9239,7 +9241,7 @@
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveActivityToStack: moving r=" + r
                         + " to stackId=" + stackId);
                 mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS,
-                        "moveActivityToStack");
+                        "moveActivityToStack", true /* animate */);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9260,7 +9262,7 @@
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
                         + " to stackId=" + stackId + " toTop=" + toTop);
                 mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS,
-                        "moveTaskToStack");
+                        "moveTaskToStack", true /* animate */);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9277,9 +9279,10 @@
      *                   and
      *                   {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
      * @param toTop If the task and stack should be moved to the top.
+     * @param animate Whether we should play an animation for the moving the task
      */
     @Override
-    public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop) {
+    public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate) {
         enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
                 "moveTaskToDockedStack()");
         synchronized (this) {
@@ -9289,7 +9292,8 @@
                         + " to createMode=" + createMode + " toTop=" + toTop);
                 mWindowManager.setDockedStackCreateMode(createMode);
                 mStackSupervisor.moveTaskToStackLocked(
-                        taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack");
+                        taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
+                        animate);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -11878,9 +11882,13 @@
                 mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
 
         final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
+        final String fsScreenshots = Settings.Secure.getString(resolver,
+                "overview_fullscreen_thumbnails");
         final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
         final boolean alwaysFinishActivities =
                 Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
+        final boolean takeFullscreenScreenshots = fsScreenshots != null &&
+                Integer.parseInt(fsScreenshots) != 0;
         final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
         final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
         final boolean forceResizable = Settings.Global.getInt(
@@ -11901,6 +11909,7 @@
             mAlwaysFinishActivities = alwaysFinishActivities;
             mForceResizableActivities = forceResizable;
             mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
+            mTakeFullscreenScreenshots = takeFullscreenScreenshots;
             // This happens before any activities are started, so we can
             // change mConfiguration in-place.
             updateConfigurationLocked(configuration, null, true);
@@ -20248,25 +20257,27 @@
     }
 
     @Override
-    public boolean switchUser(final int userId) {
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
-        String userName;
+    public boolean switchUser(final int targetUserId) {
+        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
+        UserInfo currentUserInfo;
+        UserInfo targetUserInfo;
         synchronized (this) {
-            UserInfo userInfo = mUserController.getUserInfo(userId);
-            if (userInfo == null) {
-                Slog.w(TAG, "No user info for user #" + userId);
+            int currentUserId = mUserController.getCurrentUserIdLocked();
+            currentUserInfo = mUserController.getUserInfo(currentUserId);
+            targetUserInfo = mUserController.getUserInfo(targetUserId);
+            if (targetUserInfo == null) {
+                Slog.w(TAG, "No user info for user #" + targetUserId);
                 return false;
             }
-            if (userInfo.isManagedProfile()) {
-                Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+            if (targetUserInfo.isManagedProfile()) {
+                Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
                 return false;
             }
-            userName = userInfo.name;
-            mUserController.setTargetUserIdLocked(userId);
+            mUserController.setTargetUserIdLocked(targetUserId);
         }
+        Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
         mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
-        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userId, 0,
-                userName));
+        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userNames));
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e28d198..6c68f88 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -54,6 +54,7 @@
 import android.app.ResultInfo;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
@@ -72,6 +73,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.EventLog;
 import android.util.Slog;
@@ -836,8 +838,8 @@
         }
     }
 
-    public final Bitmap screenshotActivities(ActivityRecord who) {
-        if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivities: " + who);
+    public final Bitmap screenshotActivitiesLocked(ActivityRecord who) {
+        if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivitiesLocked: " + who);
         if (who.noDisplay) {
             if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tNo display");
             return null;
@@ -852,10 +854,21 @@
 
         int w = mService.mThumbnailWidth;
         int h = mService.mThumbnailHeight;
+        float scale = 1f;
         if (w > 0) {
             if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tTaking screenshot");
+
+            // When this flag is set, we currently take the fullscreen screenshot of the activity
+            // but scaled inversely by the density.  This gives us a "good-enough" fullscreen
+            // thumbnail to use within SystemUI without using enormous amounts of memory on high
+            // density devices.
+            if (mService.mTakeFullscreenScreenshots) {
+                Context context = mService.mContext;
+                w = h = -1;
+                scale = (1f / Math.max(1f, context.getResources().getDisplayMetrics().density));
+            }
             return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY,
-                    w, h);
+                    w, h, scale);
         }
         Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h);
         return null;
@@ -914,7 +927,7 @@
         final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
         if (mService.mHasRecents
                 && (next == null || next.noDisplay || next.task != prev.task || uiSleeping)) {
-            prev.updateThumbnailLocked(screenshotActivities(prev), null);
+            prev.updateThumbnailLocked(screenshotActivitiesLocked(prev), null);
         }
         stopFullyDrawnTraceIfNeeded();
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 124d2ef..507d76d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3100,7 +3100,8 @@
                     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");
+                                FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack",
+                                true /* animate */);
                     }
 
                     // stack shouldn't contain anymore activities, so nothing to resume.
@@ -3336,7 +3337,7 @@
     }
 
     void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
-            String reason) {
+            String reason, boolean animate) {
         final TaskRecord task = anyTaskForIdLocked(taskId);
         if (task == null) {
             Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
@@ -3357,7 +3358,7 @@
             // preserve the old window until the new one is drawn. This prevents having a gap
             // between the removal and addition, in which no window is visible. We also want the
             // entrance of the new window to be properly animated.
-            mWindowManager.setReplacingWindow(topActivity.appToken);
+            mWindowManager.setReplacingWindow(topActivity.appToken, animate);
         }
         final ActivityStack stack = moveTaskToStackUncheckedLocked(
                 task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
@@ -3405,7 +3406,7 @@
             // 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,
-                    "moveTopActivityToPinnedStack");
+                    "moveTopActivityToPinnedStack", true /* animate */);
         } else {
             final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
             pinnedStack.moveActivityToStack(r);
@@ -3690,7 +3691,7 @@
     void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
         r.mLaunchTaskBehind = false;
         final TaskRecord task = r.task;
-        task.setLastThumbnailLocked(task.stack.screenshotActivities(r));
+        task.setLastThumbnailLocked(task.stack.screenshotActivitiesLocked(r));
         mRecentTasks.addLocked(task);
         mService.notifyTaskStackChangedLocked();
         mWindowManager.setAppVisibility(r.appToken, false);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 02cf87a..755db6f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -834,7 +834,7 @@
         if (stack != null) {
             final ActivityRecord resumedActivity = stack.mResumedActivity;
             if (resumedActivity != null && resumedActivity.task == this) {
-                final Bitmap thumbnail = stack.screenshotActivities(resumedActivity);
+                final Bitmap thumbnail = stack.screenshotActivitiesLocked(resumedActivity);
                 setLastThumbnailLocked(thumbnail);
             }
         }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 4b2a55f..b30905e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -61,6 +61,7 @@
 import android.os.UserManager;
 import android.os.storage.IMountService;
 import android.os.storage.StorageManager;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -641,10 +642,10 @@
         return true;
     }
 
-    void showUserSwitchDialog(int userId, String userName) {
+    void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
-        Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
-                true /* above system */);
+        Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromToUserPair.first,
+                fromToUserPair.second, true /* above system */);
         d.show();
     }
 
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 28b4096..10e88e6 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -18,9 +18,12 @@
 
 import android.app.AlertDialog;
 import android.content.Context;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -49,20 +52,26 @@
     @GuardedBy("this")
     private boolean mStartedUser;
 
-    public UserSwitchingDialog(ActivityManagerService service, Context context,
-            int userId, String userName, boolean aboveSystem) {
+    public UserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
+            UserInfo newUser, boolean aboveSystem) {
         super(context);
 
         mService = service;
-        mUserId = userId;
+        mUserId = newUser.id;
 
         // Set up the dialog contents
         setCancelable(false);
         Resources res = getContext().getResources();
         // Custom view due to alignment and font size requirements
         View view = LayoutInflater.from(getContext()).inflate(R.layout.user_switching_dialog, null);
-        ((TextView) view.findViewById(R.id.message)).setText(
-                res.getString(com.android.internal.R.string.user_switching_message, userName));
+
+        String viewMessage;
+        if (UserManager.isSplitSystemUser() && newUser.id == UserHandle.USER_SYSTEM) {
+            viewMessage = res.getString(R.string.user_logging_out_message, oldUser.name);
+        } else {
+            viewMessage = res.getString(R.string.user_switching_message, newUser.name);
+        }
+        ((TextView) view.findViewById(R.id.message)).setText(viewMessage);
         setView(view);
 
         if (aboveSystem) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fe9fe50..75886aa 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5151,12 +5151,12 @@
                     UserInfo userInfo = UserManagerService.getInstance().getUserInfo(userId);
                     killBackgroundUserProcessesWithRecordAudioPermission(userInfo);
                 }
-                UserManagerService.getInstance().setSystemControlledUserRestriction(
+                UserManagerService.getInstance().setUserRestriction(
                         UserManager.DISALLOW_RECORD_AUDIO, true, userId);
             } else if (action.equals(Intent.ACTION_USER_FOREGROUND)) {
                 // Enable audio recording for foreground user/profile
                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                UserManagerService.getInstance().setSystemControlledUserRestriction(
+                UserManagerService.getInstance().setUserRestriction(
                         UserManager.DISALLOW_RECORD_AUDIO, false, userId);
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fd036a7..558ea58 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -781,18 +781,6 @@
     @Override
     public void setUserRestriction(String key, boolean value, int userId) {
         checkManageUsersPermission("setUserRestriction");
-        if (!UserRestrictionsUtils.isSystemControlled(key)) {
-            setUserRestrictionNoCheck(key, value, userId);
-        }
-    }
-
-    @Override
-    public void setSystemControlledUserRestriction(String key, boolean value, int userId) {
-        checkSystemOrRoot("setSystemControlledUserRestriction");
-        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.
@@ -1561,8 +1549,6 @@
         final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
         final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
         final long ident = Binder.clearCallingIdentity();
-        final DevicePolicyManager devicePolicyManager = (DevicePolicyManager) mContext
-                .getSystemService(Context.DEVICE_POLICY_SERVICE);
         UserInfo userInfo;
         final int userId;
         try {
@@ -1605,22 +1591,13 @@
                         return null;
                     }
                 }
-                if (devicePolicyManager != null) {
-                    int deviceOwnerUserId = devicePolicyManager.getDeviceOwnerUserId();
-                    // If there is a device owner, completely disallow multiple user in non-split
-                    // user devices. In split user devices, no further users can be added If there
-                    // is a device owner outside of the system user.
-                    if (deviceOwnerUserId != UserHandle.USER_NULL
-                            && (!UserManager.isSplitSystemUser()
-                            || deviceOwnerUserId != UserHandle.USER_SYSTEM)) {
-                        return null;
-                    }
-                }
                 // In split system user mode, we assign the first human user the primary flag.
                 // And if there is no device owner, we also assign the admin flag to primary user.
                 if (UserManager.isSplitSystemUser()
                         && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
                     flags |= UserInfo.FLAG_PRIMARY;
+                    DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+                            mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
                     if (devicePolicyManager == null
                             || devicePolicyManager.getDeviceOwner() == null) {
                         flags |= UserInfo.FLAG_ADMIN;
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 129cbd3..85453a8 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -50,7 +50,7 @@
     private UserRestrictionsUtils() {
     }
 
-    public static final String[] USER_RESTRICTIONS = {
+    public static final Set<String> USER_RESTRICTIONS = Sets.newArraySet(
             UserManager.DISALLOW_CONFIG_WIFI,
             UserManager.DISALLOW_MODIFY_ACCOUNTS,
             UserManager.DISALLOW_INSTALL_APPS,
@@ -84,14 +84,7 @@
             UserManager.DISALLOW_SAFE_BOOT,
             UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
             UserManager.DISALLOW_RECORD_AUDIO,
-            UserManager.DISALLOW_CAMERA,
-    };
-
-    /**
-     * Set of user restrictions, which can only be enforced by the system.
-     */
-    public static final Set<String> SYSTEM_CONTROLLED_USER_RESTRICTIONS = Sets.newArraySet(
-            UserManager.DISALLOW_RECORD_AUDIO
+            UserManager.DISALLOW_CAMERA
     );
 
     /**
@@ -143,11 +136,17 @@
         }
 
         serializer.startTag(null, tag);
-        for (String key : USER_RESTRICTIONS) {
-            if (restrictions.getBoolean(key)
-                    && !NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
-                serializer.attribute(null, key, "true");
+        for (String key : restrictions.keySet()) {
+            if (NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
+                continue; // Don't persist.
             }
+            if (USER_RESTRICTIONS.contains(key)) {
+                if (restrictions.getBoolean(key)) {
+                    serializer.attribute(null, key, "true");
+                }
+                continue;
+            }
+            Log.w(TAG, "Unknown user restriction detected: " + key);
         }
         serializer.endTag(null, tag);
     }
@@ -198,14 +197,6 @@
     }
 
     /**
-     * @return true if a restriction is "system controlled"; i.e. can not be overwritten via
-     * {@link UserManager#setUserRestriction}.
-     */
-    public static boolean isSystemControlled(String restriction) {
-        return SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(restriction);
-    }
-
-    /**
      * @return true if a restriction is settable by device owner.
      */
     public static boolean canDeviceOwnerChange(String restriction) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 3f4eaac..425ff9b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -128,6 +128,8 @@
     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;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 74750a9..fdccc3f 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -281,7 +281,7 @@
         }
     }
 
-    void broadcastDragEndedLw() {
+    private void broadcastDragEndedLw() {
         final int myPid = Process.myPid();
 
         if (WindowManagerService.DEBUG_DRAG) {
@@ -313,6 +313,9 @@
     }
 
     void endDragLw() {
+        if (mAnimation != null) {
+            return;
+        }
         if (!mDragResult) {
             mAnimation = createReturnAnimationLocked();
             mService.scheduleAnimationLocked();
@@ -322,7 +325,7 @@
     }
 
 
-    void cleanUpDragLw() {
+    private void cleanUpDragLw() {
         broadcastDragEndedLw();
 
         // stop intercepting input
@@ -336,6 +339,9 @@
     }
 
     void notifyMoveLw(float x, float y) {
+        if (mAnimation != null) {
+            return;
+        }
         mCurrentX = x;
         mCurrentY = y;
 
@@ -410,6 +416,9 @@
     // result from the recipient.
     boolean notifyDropLw(WindowState touchedWin, DropPermissionHolder dropPermissionHolder,
             float x, float y) {
+        if (mAnimation != null) {
+            return false;
+        }
         mCurrentX = x;
         mCurrentY = y;
 
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index f5e4e3b..804830e 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -196,7 +196,7 @@
                                     ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
                                     : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
                             mService.mActivityManager.moveTaskToDockedStack(
-                                    mTask.mTaskId, createMode, true /*toTop*/);
+                                    mTask.mTaskId, createMode, true /*toTop*/, true /* animate */);
                         }
                     } catch(RemoteException e) {}
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e741c45..c7fccc3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2079,7 +2079,7 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mWillReplaceWindow) {
+        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
             return;
         }
         atoken.allDrawn = false;
@@ -2951,7 +2951,7 @@
             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 fullscreen = win != null && win.isFrameFullscreen(displayInfo);
             final boolean freeform = win != null && win.inFreeformWorkspace();
             final boolean docked = win != null && win.inDockedWorkspace();
             if (win != null) {
@@ -5717,7 +5717,7 @@
             @Override
             public void run() {
                 Bitmap bm = screenshotApplicationsInner(null, Display.DEFAULT_DISPLAY, -1, -1,
-                        true);
+                        true, 1f);
                 try {
                     receiver.send(bm);
                 } catch (RemoteException e) {
@@ -5736,18 +5736,20 @@
      * @param displayId the Display to take a screenshot of.
      * @param width the width of the target bitmap
      * @param height the height of the target bitmap
+     * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
      */
     @Override
-    public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height) {
+    public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height,
+            float frameScale) {
         if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
                 "screenshotApplications()")) {
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
         }
-        return screenshotApplicationsInner(appToken, displayId, width, height, false);
+        return screenshotApplicationsInner(appToken, displayId, width, height, false, frameScale);
     }
 
     Bitmap screenshotApplicationsInner(IBinder appToken, int displayId, int width, int height,
-            boolean includeFullDisplay) {
+            boolean includeFullDisplay, float frameScale) {
         final DisplayContent displayContent;
         synchronized(mWindowMap) {
             displayContent = getDisplayContentLocked(displayId);
@@ -5876,7 +5878,7 @@
                         screenshotReady = true;
                     }
 
-                    if (ws.isFullscreen(dw, dh) && ws.isOpaqueDrawn()){
+                    if (ws.isObscuringFullscreen(displayInfo)){
                         break;
                     }
                 }
@@ -5927,10 +5929,10 @@
                 }
 
                 if (width < 0) {
-                    width = frame.width();
+                    width = (int) (frame.width() * frameScale);
                 }
                 if (height < 0) {
-                    height = frame.height();
+                    height = (int) (frame.height() * frameScale);
                 }
 
                 // Tell surface flinger what part of the image to crop. Take the top
@@ -8569,7 +8571,8 @@
             } else if (wtoken != null) {
                 winAnimator.mAnimLayer =
                         w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
-                if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w) {
+                if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w
+                        && wtoken.mAnimateReplacingWindow) {
                     // 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.
@@ -10107,7 +10110,7 @@
      * a window.
      * @param token Application token for which the activity will be relaunched.
      */
-    public void setReplacingWindow(IBinder token) {
+    public void setReplacingWindow(IBinder token, boolean animate) {
         synchronized (mWindowMap) {
             AppWindowToken appWindowToken = findAppWindowToken(token);
             if (appWindowToken == null) {
@@ -10118,13 +10121,16 @@
                     + " as replacing window.");
             appWindowToken.mWillReplaceWindow = true;
             appWindowToken.mHasReplacedWindow = false;
+            appWindowToken.mAnimateReplacingWindow = animate;
 
-            // Set-up dummy animation so we can start treating windows associated with this token
-            // like they are in transition before the new app window is ready for us to run the
-            // real transition animation.
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                    "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
-            appWindowToken.mAppAnimator.setDummyAnimation();
+            if (animate) {
+                // Set-up dummy animation so we can start treating windows associated with this
+                // token like they are in transition before the new app window is ready for us to
+                // run the real transition animation.
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
+                appWindowToken.mAppAnimator.setDummyAnimation();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c6f7f4c..01db707 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1250,9 +1250,20 @@
                 && (mAttachedWindow == null || !mAttachedWindow.hasMoved());
     }
 
-    boolean isFullscreen(int screenWidth, int screenHeight) {
-        return mFrame.left <= 0 && mFrame.top <= 0 &&
-                mFrame.right >= screenWidth && mFrame.bottom >= screenHeight;
+    boolean isObscuringFullscreen(final DisplayInfo displayInfo) {
+        Task task = getTask();
+        if (task != null && task.mStack != null && !task.mStack.isFullscreen()) {
+            return false;
+        }
+        if (!isOpaqueDrawn() || !isFrameFullscreen(displayInfo)) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean isFrameFullscreen(final DisplayInfo displayInfo) {
+        return mFrame.left <= 0 && mFrame.top <= 0
+                && mFrame.right >= displayInfo.appWidth && mFrame.bottom >= displayInfo.appHeight;
     }
 
     boolean isConfigChanged() {
@@ -1389,6 +1400,7 @@
                 && token.mHasReplacedWindow) {
             if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
             token.mWillReplaceWindow = false;
+            token.mAnimateReplacingWindow = false;
             token.mReplacingRemoveRequested = false;
             token.mReplacingWindow = null;
             token.mHasReplacedWindow = false;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 004307e..93c2ff6 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1106,7 +1106,7 @@
             applyDecorRect(w.mDecorFrame);
         }
 
-        final boolean fullscreen = w.isFullscreen(displayInfo.appWidth, displayInfo.appHeight);
+        final boolean fullscreen = w.isFrameFullscreen(displayInfo);
         final boolean isFreeformResizing =
                 w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
         final Rect clipRect = mTmpClipRect;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 979f55b..6292c21 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -661,7 +661,7 @@
                 // Update effect.
                 w.mObscured = mObscured;
                 if (!mObscured) {
-                    handleNotObscuredLocked(w, innerDw, innerDh);
+                    handleNotObscuredLocked(w, displayInfo);
                 }
 
                 w.applyDimLayerIfNeeded();
@@ -1332,16 +1332,14 @@
 
     /**
      * @param w WindowState this method is applied to.
-     * @param innerDw Width of app window.
-     * @param innerDh Height of app window.
+     * @param dispInfo info of the display that the window's obscuring state is checked against.
      */
-    private void handleNotObscuredLocked(final WindowState w, final int innerDw, final int innerDh) {
+    private void handleNotObscuredLocked(final WindowState w, final DisplayInfo dispInfo) {
         final WindowManager.LayoutParams attrs = w.mAttrs;
         final int attrFlags = attrs.flags;
         final boolean canBeSeen = w.isDisplayedLw();
-        final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
 
-        if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
+        if (canBeSeen && w.isObscuringFullscreen(dispInfo)) {
             // This window completely covers everything behind it,
             // so we want to leave all of them as undimmed (for
             // performance reasons).
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3ae2751..8beee73 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1386,11 +1386,11 @@
             }
             migrated = true;
 
-            // Migrate user 0 restrictions to DO, except for "system" restrictions.
+            // Migrate user 0 restrictions to DO.
             final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
 
             migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
-                    /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+                    /* exceptionList =*/ null);
 
             // Push DO user restrictions to user manager.
             pushUserRestrictions(UserHandle.USER_SYSTEM);
@@ -1402,7 +1402,6 @@
         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);
@@ -1446,15 +1445,15 @@
         final Bundle origRestrictions = mUserManagerInternal.getBaseUserRestrictions(
                 user.getIdentifier());
 
-        final Bundle newSystemRestrictions = new Bundle();
+        final Bundle newBaseRestrictions = 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);
+            if (exceptionList!= null && exceptionList.contains(key)) {
+                newBaseRestrictions.putBoolean(key, true);
             } else {
                 newOwnerRestrictions.putBoolean(key, true);
             }
@@ -1462,11 +1461,11 @@
 
         if (VERBOSE_LOG) {
             Log.v(LOG_TAG, "origRestrictions=" + origRestrictions);
-            Log.v(LOG_TAG, "newSystemRestrictions=" + newSystemRestrictions);
+            Log.v(LOG_TAG, "newBaseRestrictions=" + newBaseRestrictions);
             Log.v(LOG_TAG, "newOwnerRestrictions=" + newOwnerRestrictions);
         }
         mUserManagerInternal.setBaseUserRestrictionsByDpmsForMigration(user.getIdentifier(),
-                newSystemRestrictions);
+                newBaseRestrictions);
 
         if (admin != null) {
             admin.ensureUserRestrictions().clear();
@@ -4628,11 +4627,6 @@
         }
     }
 
-    @Override
-    public int getDeviceOwnerUserId() {
-        return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
-    }
-
     // Returns the active device owner or null if there is no device owner.
     @VisibleForTesting
     ActiveAdmin getDeviceOwnerAdminLocked() {
@@ -6847,4 +6841,30 @@
         final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
         return targetSdkVersion;
     }
+
+    @Override
+    public boolean isManagedProfile(ComponentName admin) {
+        synchronized (this) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        }
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        final UserInfo user;
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            user = mUserManager.getUserInfo(callingUserId);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+        return user != null && user.isManagedProfile();
+    }
+
+    @Override
+    public boolean isSystemOnlyUser(ComponentName admin) {
+        synchronized (this) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        return UserManager.isSplitSystemUser() && callingUserId == UserHandle.USER_SYSTEM;
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index dfa9f8f..f32f209 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,16 +145,13 @@
 
         // Check the new base restrictions.
         DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_RECORD_AUDIO
-                ),
+                DpmTestUtils.newRestrictions(),
                 newBaseRestrictions.get(UserHandle.USER_SYSTEM));
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
                         UserManager.DISALLOW_SMS,
-                        UserManager.DISALLOW_OUTGOING_CALLS,
-                        UserManager.DISALLOW_RECORD_AUDIO
+                        UserManager.DISALLOW_OUTGOING_CALLS
                 ),
                 newBaseRestrictions.get(10));
 
@@ -162,28 +159,30 @@
                 DpmTestUtils.newRestrictions(
                         UserManager.DISALLOW_SMS,
                         UserManager.DISALLOW_OUTGOING_CALLS,
-                        UserManager.DISALLOW_WALLPAPER,
-                        UserManager.DISALLOW_RECORD_AUDIO
+                        UserManager.DISALLOW_WALLPAPER
                 ),
                 newBaseRestrictions.get(11));
 
         // Check the new owner restrictions.
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_ADD_USER
+                        UserManager.DISALLOW_ADD_USER,
+                        UserManager.DISALLOW_RECORD_AUDIO
                 ),
                 dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions());
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
                         UserManager.DISALLOW_REMOVE_USER,
-                        UserManager.DISALLOW_WALLPAPER
+                        UserManager.DISALLOW_WALLPAPER,
+                        UserManager.DISALLOW_RECORD_AUDIO
                 ),
                 dpms.getProfileOwnerAdminLocked(10).ensureUserRestrictions());
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_REMOVE_USER
+                        UserManager.DISALLOW_REMOVE_USER,
+                        UserManager.DISALLOW_RECORD_AUDIO
                 ),
                 dpms.getProfileOwnerAdminLocked(11).ensureUserRestrictions());
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
new file mode 100644
index 0000000..5542a4f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.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.pm;
+
+import com.android.server.devicepolicy.DpmTestUtils;
+
+import android.os.Bundle;
+import android.os.UserManager;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+/**
+ * Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
+ *
+ * <p>Run with:<pre>
+   m FrameworksServicesTests &&
+   adb install \
+     -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+   adb shell am instrument -e class com.android.server.pm.UserRestrictionsUtilsTest \
+     -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public class UserRestrictionsUtilsTest extends AndroidTestCase {
+    public void testNonNull() {
+        Bundle out = UserRestrictionsUtils.nonNull(null);
+        assertNotNull(out);
+        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+
+        Bundle in = new Bundle();
+        assertSame(in, UserRestrictionsUtils.nonNull(in));
+    }
+
+    public void testIsEmpty() {
+        assertTrue(UserRestrictionsUtils.isEmpty(null));
+        assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
+        assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a")));
+    }
+
+    public void testClone() {
+        Bundle in = new Bundle();
+        Bundle out = UserRestrictionsUtils.clone(in);
+        assertNotSame(in, out);
+        DpmTestUtils.assertRestrictions(out, new Bundle());
+
+        out = UserRestrictionsUtils.clone(null);
+        assertNotNull(out);
+        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+    }
+
+    public void testMerge() {
+        Bundle a = DpmTestUtils.newRestrictions("a", "d");
+        Bundle b = DpmTestUtils.newRestrictions("b", "d", "e");
+
+        UserRestrictionsUtils.merge(a, b);
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+        UserRestrictionsUtils.merge(a, null);
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+        try {
+            UserRestrictionsUtils.merge(a, a);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testCanDeviceOwnerChange() {
+        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
+        assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
+    }
+
+    public void testCanProfileOwnerChange() {
+        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_WALLPAPER));
+        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADD_USER));
+        assertTrue(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADJUST_VOLUME));
+    }
+
+    public void testSortToGlobalAndLocal() {
+        final Bundle local = new Bundle();
+        final Bundle global = new Bundle();
+
+        UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local);
+        assertEquals(0, global.size());
+        assertEquals(0, local.size());
+
+        UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local);
+        assertEquals(0, global.size());
+        assertEquals(0, local.size());
+
+        UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_ADJUST_VOLUME,
+                UserManager.DISALLOW_UNMUTE_MICROPHONE,
+                UserManager.DISALLOW_USB_FILE_TRANSFER,
+                UserManager.DISALLOW_CONFIG_TETHERING,
+                UserManager.DISALLOW_OUTGOING_BEAM,
+                UserManager.DISALLOW_APPS_CONTROL
+        ), global, local);
+
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+                // These can be set by PO too, but when DO sets them, they're global.
+                UserManager.DISALLOW_ADJUST_VOLUME,
+                UserManager.DISALLOW_UNMUTE_MICROPHONE,
+
+                // These can only be set by DO.
+                UserManager.DISALLOW_USB_FILE_TRANSFER,
+                UserManager.DISALLOW_CONFIG_TETHERING
+        ), global);
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+                // They can be set by both DO/PO.
+                UserManager.DISALLOW_OUTGOING_BEAM,
+                UserManager.DISALLOW_APPS_CONTROL
+        ), local);
+    }
+
+    public void testAreEqual() {
+        assertTrue(UserRestrictionsUtils.areEqual(
+                null,
+                null));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                null,
+                Bundle.EMPTY));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                Bundle.EMPTY,
+                null));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                Bundle.EMPTY,
+                Bundle.EMPTY));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                new Bundle(),
+                Bundle.EMPTY));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                null,
+                DpmTestUtils.newRestrictions("a")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a"),
+                null));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a"),
+                DpmTestUtils.newRestrictions("a")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a"),
+                DpmTestUtils.newRestrictions("a", "b")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a", "b"),
+                DpmTestUtils.newRestrictions("a")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("b", "a"),
+                DpmTestUtils.newRestrictions("a", "a")));
+
+        // Make sure false restrictions are handled correctly.
+        final Bundle a = DpmTestUtils.newRestrictions("a");
+        a.putBoolean("b", true);
+
+        final Bundle b = DpmTestUtils.newRestrictions("a");
+        b.putBoolean("b", false);
+
+        assertFalse(UserRestrictionsUtils.areEqual(a, b));
+        assertFalse(UserRestrictionsUtils.areEqual(b, a));
+    }
+}
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index 7ea26b3..ab4d284 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -18,7 +18,6 @@
 #define AAPT_DIAGNOSTICS_H
 
 #include "Source.h"
-
 #include "util/StringPiece.h"
 #include "util/Util.h"
 
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 02fe59c..c2ddb5c 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -711,6 +711,46 @@
         }
     }
 
+    Maybe<int32_t> maybeMin, maybeMax;
+
+    if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) {
+        StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value());
+        if (!minStr.empty()) {
+            android::Res_value value;
+            if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) {
+                maybeMin = static_cast<int32_t>(value.data);
+            }
+        }
+
+        if (!maybeMin) {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "invalid 'min' value '" << minStr << "'");
+            return false;
+        }
+    }
+
+    if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) {
+        StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value());
+        if (!maxStr.empty()) {
+            android::Res_value value;
+            if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) {
+                maybeMax = static_cast<int32_t>(value.data);
+            }
+        }
+
+        if (!maybeMax) {
+            mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                         << "invalid 'max' value '" << maxStr << "'");
+            return false;
+        }
+    }
+
+    if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
+        mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+                     << "'min' and 'max' can only be used when format='integer'");
+        return false;
+    }
+
     struct SymbolComparator {
         bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
             return a.symbol.name.value() < b.symbol.name.value();
@@ -794,6 +834,13 @@
     std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
     attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
     attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
+    if (maybeMin) {
+        attr->minInt = maybeMin.value();
+    }
+
+    if (maybeMax) {
+        attr->maxInt = maybeMax.value();
+    }
     outResource->value = std::move(attr);
     return true;
 }
@@ -872,6 +919,7 @@
     }
 
     transformReferenceFromNamespace(parser, u"", &maybeKey.value());
+    maybeKey.value().setSource(source);
 
     std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
     if (!value) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index ab16424..6e0812b 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -138,6 +138,22 @@
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
 }
 
+TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
+    std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
+    ASSERT_TRUE(testParse(input));
+
+    Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+    ASSERT_NE(nullptr, attr);
+    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
+    EXPECT_EQ(10, attr->minInt);
+    EXPECT_EQ(23, attr->maxInt);
+}
+
+TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
+    std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
+    ASSERT_FALSE(testParse(input));
+}
+
 TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
     std::string input = "<declare-styleable name=\"Styleable\">\n"
                         "  <attr name=\"foo\" />\n"
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 5550f19..04c375f 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,9 +15,9 @@
  */
 
 #include "Resource.h"
+#include "ResourceUtils.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-
 #include "util/Util.h"
 #include "flatten/ResourceTypeExtensions.h"
 
@@ -216,7 +216,7 @@
             *out << "(null)";
             break;
         case android::Res_value::TYPE_INT_DEC:
-            *out << "(integer) " << value.data;
+            *out << "(integer) " << static_cast<int32_t>(value.data);
             break;
         case android::Res_value::TYPE_INT_HEX:
             *out << "(integer) " << std::hex << value.data << std::dec;
@@ -237,7 +237,10 @@
     }
 }
 
-Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
+Attribute::Attribute(bool w, uint32_t t) :
+        weak(w), typeMask(t),
+        minInt(std::numeric_limits<int32_t>::min()),
+        maxInt(std::numeric_limits<int32_t>::max()) {
 }
 
 bool Attribute::isWeak() const {
@@ -361,6 +364,81 @@
     }
 }
 
+static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+                                          const Item* value) {
+    *msg << "expected";
+    if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+        *msg << " boolean";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+        *msg << " color";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+        *msg << " dimension";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+        *msg << " enum";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+        *msg << " flags";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+        *msg << " float";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+        *msg << " fraction";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+        *msg << " integer";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+        *msg << " reference";
+    }
+
+    if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+        *msg << " string";
+    }
+
+    *msg << " but got " << *value;
+}
+
+bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
+    android::Res_value val = {};
+    item->flatten(&val);
+
+    // Always allow references.
+    const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
+    if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+        if (outMsg) {
+            buildAttributeMismatchMessage(outMsg, this, item);
+        }
+        return false;
+
+    } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
+            android::ResTable_map::TYPE_INTEGER) {
+        if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
+            if (outMsg) {
+                *outMsg << *item << " is less than minimum integer " << minInt;
+            }
+            return false;
+        } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
+            if (outMsg) {
+                *outMsg << *item << " is greater than maximum integer " << maxInt;
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
 Style* Style::clone(StringPool* newPool) const {
     Style* style = new Style();
     style->parent = parent;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 7ae346a..a038282 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,9 +17,10 @@
 #ifndef AAPT_RESOURCE_VALUES_H
 #define AAPT_RESOURCE_VALUES_H
 
-#include "util/Maybe.h"
+#include "Diagnostics.h"
 #include "Resource.h"
 #include "StringPool.h"
+#include "util/Maybe.h"
 
 #include <array>
 #include <androidfw/ResourceTypes.h>
@@ -233,8 +234,8 @@
 
 	bool weak;
     uint32_t typeMask;
-    uint32_t minInt;
-    uint32_t maxInt;
+    int32_t minInt;
+    int32_t maxInt;
     std::vector<Symbol> symbols;
 
     Attribute(bool w, uint32_t t = 0u);
@@ -243,6 +244,7 @@
     Attribute* clone(StringPool* newPool) const override;
     void printMask(std::ostream* out) const;
     void print(std::ostream* out) const override;
+    bool matches(const Item* item, DiagMessage* outMsg) const;
 };
 
 struct Style : public BaseValue<Style> {
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 8af203c..319528e 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -58,6 +58,10 @@
     return out;
 }
 
+inline bool operator==(const Source& lhs, const Source& rhs) {
+    return lhs.path == rhs.path && lhs.line == rhs.line;
+}
+
 inline bool operator<(const Source& lhs, const Source& rhs) {
     int cmp = lhs.path.compare(rhs.path);
     if (cmp < 0) return true;
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index acf5bb5..02bff2c 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -67,6 +67,28 @@
 };
 
 /**
+ * New types for a ResTable_map.
+ */
+struct ExtendedResTableMapTypes {
+    enum {
+        /**
+         * Type that contains the source path of the next item in the map.
+         */
+        ATTR_SOURCE_PATH = Res_MAKEINTERNAL(0xffff),
+
+        /**
+         * Type that contains the source line of the next item in the map.
+         */
+        ATTR_SOURCE_LINE = Res_MAKEINTERNAL(0xfffe),
+
+        /**
+         * Type that contains the comment of the next item in the map.
+         */
+        ATTR_COMMENT = Res_MAKEINTERNAL(0xfffd)
+    };
+};
+
+/**
  * Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool.
  */
 struct FileExport_header {
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 636e977..f42e6b7 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -108,15 +108,19 @@
     SymbolWriter* mSymbols;
     FlatEntry* mEntry;
     BigBuffer* mBuffer;
+    StringPool* mSourcePool;
+    StringPool* mCommentPool;
     bool mUseExtendedChunks;
+
     size_t mEntryCount = 0;
     Maybe<uint32_t> mParentIdent;
     Maybe<ResourceNameRef> mParentName;
 
     MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
+                      StringPool* sourcePool, StringPool* commentPool,
                       bool useExtendedChunks) :
-            mSymbols(symbols), mEntry(entry), mBuffer(buffer),
-            mUseExtendedChunks(useExtendedChunks) {
+            mSymbols(symbols), mEntry(entry), mBuffer(buffer), mSourcePool(sourcePool),
+            mCommentPool(commentPool), mUseExtendedChunks(useExtendedChunks) {
     }
 
     void flattenKey(Reference* key, ResTable_map* outEntry) {
@@ -158,6 +162,32 @@
         mEntryCount++;
     }
 
+    void flattenMetaData(Value* value) {
+        if (!mUseExtendedChunks) {
+            return;
+        }
+
+        Reference key(ResourceId{ ExtendedResTableMapTypes::ATTR_SOURCE_PATH });
+        StringPool::Ref sourcePathRef = mSourcePool->makeRef(
+                util::utf8ToUtf16(value->getSource().path));
+        BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+                            static_cast<uint32_t>(sourcePathRef.getIndex()));
+        flattenEntry(&key, &val);
+
+        if (value->getSource().line) {
+            key.id = ResourceId(ExtendedResTableMapTypes::ATTR_SOURCE_LINE);
+            val.value.data = static_cast<uint32_t>(value->getSource().line.value());
+            flattenEntry(&key, &val);
+        }
+
+        if (!value->getComment().empty()) {
+            key.id = ResourceId(ExtendedResTableMapTypes::ATTR_COMMENT);
+            StringPool::Ref commentRef = mCommentPool->makeRef(value->getComment());
+            val.value.data = static_cast<uint32_t>(commentRef.getIndex());
+            flattenEntry(&key, &val);
+        }
+    }
+
     void visit(Attribute* attr) override {
         {
             Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
@@ -165,6 +195,18 @@
             flattenEntry(&key, &val);
         }
 
+        if (attr->minInt != std::numeric_limits<int32_t>::min()) {
+            Reference key(ResourceId{ ResTable_map::ATTR_MIN });
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
+            flattenEntry(&key, &val);
+        }
+
+        if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
+            Reference key(ResourceId{ ResTable_map::ATTR_MAX });
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
+            flattenEntry(&key, &val);
+        }
+
         for (Attribute::Symbol& s : attr->symbols) {
             BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
             flattenEntry(&s.symbol, &val);
@@ -199,6 +241,7 @@
 
         for (Style::Entry& entry : style->entries) {
             flattenEntry(&entry.key, entry.value.get());
+            flattenMetaData(&entry.key);
         }
     }
 
@@ -206,6 +249,7 @@
         for (auto& attrRef : styleable->entries) {
             BinaryPrimitive val(Res_value{});
             flattenEntry(&attrRef, &val);
+            flattenMetaData(&attrRef);
         }
     }
 
@@ -215,6 +259,7 @@
             flattenValue(item.get(), outEntry);
             outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
             mEntryCount++;
+            flattenMetaData(item.get());
         }
     }
 
@@ -258,6 +303,7 @@
 
             Reference key(q);
             flattenEntry(&key, plural->values[i].get());
+            flattenMetaData(plural->values[i].get());
         }
     }
 };
@@ -377,7 +423,8 @@
         } else {
             const size_t beforeEntry = buffer->size();
             ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
-            MapFlattenVisitor visitor(mSymbols, entry, buffer, mOptions.useExtendedChunks);
+            MapFlattenVisitor visitor(mSymbols, entry, buffer, mSourcePool, mSourcePool,
+                                      mOptions.useExtendedChunks);
             entry->value->accept(&visitor);
             outEntry->count = util::hostToDevice32(visitor.mEntryCount);
             if (visitor.mParentName) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 4ffb980..7030603 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -262,4 +262,55 @@
     EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
 }
 
+TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
+    Attribute attr(false);
+    attr.typeMask = android::ResTable_map::TYPE_INTEGER;
+    attr.minInt = 10;
+    attr.maxInt = 23;
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+                      util::make_unique<Attribute>(attr))
+            .build();
+
+    ResourceTable result;
+    ASSERT_TRUE(flatten(table.get(), &result));
+
+    Attribute* actualAttr = test::getValue<Attribute>(&result, u"@android:attr/foo");
+    ASSERT_NE(nullptr, actualAttr);
+    EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
+    EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
+    EXPECT_EQ(attr.minInt, actualAttr->minInt);
+    EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
+}
+
+TEST_F(TableFlattenerTest, FlattenSourceAndCommentsForChildrenOfCompoundValues) {
+    Style style;
+    Reference key(test::parseNameOrDie(u"@android:attr/foo"));
+    key.id = ResourceId(0x01010000);
+    key.setSource(Source("test").withLine(2));
+    key.setComment(StringPiece16(u"comment"));
+    style.entries.push_back(Style::Entry{ key, util::make_unique<Id>() });
+
+    test::ResourceTableBuilder builder = test::ResourceTableBuilder();
+    std::unique_ptr<ResourceTable> table = builder
+            .setPackageId(u"android", 0x01)
+            .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+                      test::AttributeBuilder().build())
+            .addValue(u"@android:style/foo", ResourceId(0x01020000),
+                      std::unique_ptr<Style>(style.clone(builder.getStringPool())))
+            .build();
+
+    ResourceTable result;
+    ASSERT_TRUE(flatten(table.get(), &result));
+
+    Style* actualStyle = test::getValue<Style>(&result, u"@android:style/foo");
+    ASSERT_NE(nullptr, actualStyle);
+    ASSERT_EQ(1u, actualStyle->entries.size());
+
+    Reference* actualKey = &actualStyle->entries[0].key;
+    EXPECT_EQ(key.getSource(), actualKey->getSource());
+    EXPECT_EQ(key.getComment(), actualKey->getComment());
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 4b82537..2743539 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -78,52 +78,6 @@
         return value;
     }
 
-    void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
-                                       const Item* value) {
-        *msg << "expected";
-        if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
-            *msg << " boolean";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
-            *msg << " color";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
-            *msg << " dimension";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
-            *msg << " enum";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
-            *msg << " flags";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
-            *msg << " float";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
-            *msg << " fraction";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
-            *msg << " integer";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
-            *msg << " reference";
-        }
-
-        if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
-            *msg << " string";
-        }
-
-        *msg << " but got " << *value;
-    }
-
 public:
     using ValueVisitor::visit;
 
@@ -175,32 +129,25 @@
                 entry.value->accept(this);
 
                 // Now verify that the type of this item is compatible with the attribute it
-                // is defined for.
-                android::Res_value val = {};
-                entry.value->flatten(&val);
-
-                // Always allow references.
-                const uint32_t typeMask = symbol->attribute->typeMask |
-                        android::ResTable_map::TYPE_REFERENCE;
-
-                if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+                // is defined for. We pass `nullptr` as the DiagMessage so that this check is
+                // fast and we avoid creating a DiagMessage when the match is successful.
+                if (!symbol->attribute->matches(entry.value.get(), nullptr)) {
                     // The actual type of this item is incompatible with the attribute.
-                    DiagMessage msg(style->getSource());
-                    buildAttributeMismatchMessage(&msg, symbol->attribute.get(), entry.value.get());
+                    DiagMessage msg(entry.key.getSource());
+
+                    // Call the matches method again, this time with a DiagMessage so we fill
+                    // in the actual error message.
+                    symbol->attribute->matches(entry.value.get(), &msg);
                     mContext->getDiagnostics()->error(msg);
                     mError = true;
                 }
+
             } else {
-                DiagMessage msg(style->getSource());
+                DiagMessage msg(entry.key.getSource());
                 msg << "style attribute '";
-                if (entry.key.name) {
-                    msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
-                } else {
-                    msg << entry.key.id.value();
-                }
+                ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference);
                 msg << "' " << errStr;
                 mContext->getDiagnostics()->error(msg);
-                mContext->getDiagnostics()->note(DiagMessage(style->getSource()) << entry.key);
                 mError = true;
             }
         }
@@ -249,16 +196,12 @@
         CallSite* callSite, std::string* outError) {
     const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
     if (!symbol) {
-        std::stringstream errStr;
-        errStr << "not found";
-        if (outError) *outError = errStr.str();
+        if (outError) *outError = "not found";
         return nullptr;
     }
 
     if (!isSymbolVisible(*symbol, reference, *callSite)) {
-        std::stringstream errStr;
-        errStr << "is private";
-        if (outError) *outError = errStr.str();
+        if (outError) *outError = "is private";
         return nullptr;
     }
     return symbol;
@@ -275,9 +218,7 @@
     }
 
     if (!symbol->attribute) {
-        std::stringstream errStr;
-        errStr << "is not an attribute";
-        if (outError) *outError = errStr.str();
+        if (outError) *outError = "is not an attribute";
         return nullptr;
     }
     return symbol;
@@ -294,14 +235,26 @@
     }
 
     if (!symbol->attribute) {
-        std::stringstream errStr;
-        errStr << "is not an attribute";
-        if (outError) *outError = errStr.str();
+        if (outError) *outError = "is not an attribute";
         return {};
     }
     return xml::AaptAttribute{ symbol->id, *symbol->attribute };
 }
 
+void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+                                        const Reference& transformed) {
+    assert(outMsg);
+
+    if (orig.name) {
+        *outMsg << orig.name.value();
+        if (transformed.name.value() != orig.name.value()) {
+            *outMsg << " (aka " << transformed.name.value() << ")";
+        }
+    } else {
+        *outMsg << orig.id.value();
+    }
+}
+
 bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
                                     ISymbolTable* symbols, xml::IPackageDeclStack* decls,
                                     CallSite* callSite) {
@@ -322,15 +275,7 @@
 
     DiagMessage errorMsg(reference->getSource());
     errorMsg << "resource ";
-    if (reference->name) {
-        errorMsg << reference->name.value();
-        if (transformedReference.name.value() != reference->name.value()) {
-            errorMsg << " (aka " << transformedReference.name.value() << ")";
-        }
-    } else {
-        errorMsg << reference->id.value();
-    }
-
+    writeResourceName(&errorMsg, *reference, transformedReference);
     errorMsg << " " << errStr;
     context->getDiagnostics()->error(errorMsg);
     return false;
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index 6f11d58..a0eb00c 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -80,6 +80,13 @@
                                                          std::string* outError);
 
     /**
+     * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)"
+     * syntax.
+     */
+    static void writeResourceName(DiagMessage* outMsg, const Reference& orig,
+                                  const Reference& transformed);
+
+    /**
      * Transforms the package name of the reference to the fully qualified package name using
      * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
      * to the reference at the callsite, the reference is updated with an ID.
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index d04181d..6ad2f9c 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -61,7 +61,7 @@
         if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
             // This resource has an Attribute.
             if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
-                symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
+                symbol->attribute = util::make_unique<Attribute>(*attr);
             } else {
                 return {};
             }
@@ -77,7 +77,6 @@
     return symbol.get();
 }
 
-
 static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
                                                                     ResourceId id) {
     // Try as a bag.
@@ -103,29 +102,40 @@
 
     if (s->attribute) {
         for (size_t i = 0; i < (size_t) count; i++) {
-            if (!Res_INTERNALID(entry[i].map.name.ident)) {
-                android::ResTable::resource_name entryName;
-                if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) {
-                    table.unlockBag(entry);
-                    return nullptr;
+            const android::ResTable_map& mapEntry = entry[i].map;
+            if (Res_INTERNALID(mapEntry.name.ident)) {
+                switch (mapEntry.name.ident) {
+                case android::ResTable_map::ATTR_MIN:
+                    s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data);
+                    break;
+                case android::ResTable_map::ATTR_MAX:
+                    s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data);
+                    break;
                 }
-
-                const ResourceType* parsedType = parseResourceType(
-                        StringPiece16(entryName.type, entryName.typeLen));
-                if (!parsedType) {
-                    table.unlockBag(entry);
-                    return nullptr;
-                }
-
-                Attribute::Symbol symbol;
-                symbol.symbol.name = ResourceNameRef(
-                        StringPiece16(entryName.package, entryName.packageLen),
-                        *parsedType,
-                        StringPiece16(entryName.name, entryName.nameLen)).toResourceName();
-                symbol.symbol.id = ResourceId(entry[i].map.name.ident);
-                symbol.value = entry[i].map.value.data;
-                s->attribute->symbols.push_back(std::move(symbol));
+                continue;
             }
+
+            android::ResTable::resource_name entryName;
+            if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
+                table.unlockBag(entry);
+                return nullptr;
+            }
+
+            const ResourceType* parsedType = parseResourceType(
+                    StringPiece16(entryName.type, entryName.typeLen));
+            if (!parsedType) {
+                table.unlockBag(entry);
+                return nullptr;
+            }
+
+            Attribute::Symbol symbol;
+            symbol.symbol.name = ResourceName(
+                    StringPiece16(entryName.package, entryName.packageLen),
+                    *parsedType,
+                    StringPiece16(entryName.name, entryName.nameLen));
+            symbol.symbol.id = ResourceId(mapEntry.name.ident);
+            symbol.value = mapEntry.value.data;
+            s->attribute->symbols.push_back(std::move(symbol));
         }
     }
     table.unlockBag(entry);
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 9ca694a..f8e3d03 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -36,6 +36,10 @@
 public:
     ResourceTableBuilder() = default;
 
+    StringPool* getStringPool() {
+        return &mTable->stringPool;
+    }
+
     ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) {
         ResourceTablePackage* package = mTable->createPackage(packageName, id);
         assert(package);
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 49625b5..5cc7aa7 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -575,11 +575,10 @@
 
         Source source = mSource;
         if (sourceBlock) {
-            size_t len;
-            const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index),
-                                                    &len);
-            if (str) {
-                source.path.assign(str, len);
+            StringPiece path = util::getString8(mSourcePool,
+                                                util::deviceToHost32(sourceBlock->path.index));
+            if (!path.empty()) {
+                source.path = path.toString();
             }
             source.line = util::deviceToHost32(sourceBlock->line);
         }
@@ -728,6 +727,16 @@
     }
 
     for (const ResTable_map& mapEntry : map) {
+        if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+            if (style->entries.empty()) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "out-of-sequence meta data in style");
+                return {};
+            }
+            collectMetaData(mapEntry, &style->entries.back().key);
+            continue;
+        }
+
         style->entries.emplace_back();
         Style::Entry& styleEntry = style->entries.back();
 
@@ -771,12 +780,20 @@
         attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
     }
 
-    if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
-        for (const ResTable_map& mapEntry : map) {
-            if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
-                continue;
+    for (const ResTable_map& mapEntry : map) {
+        if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+            switch (util::deviceToHost32(mapEntry.name.ident)) {
+            case ResTable_map::ATTR_MIN:
+                attr->minInt = static_cast<int32_t>(mapEntry.value.data);
+                break;
+            case ResTable_map::ATTR_MAX:
+                attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
+                break;
             }
+            continue;
+        }
 
+        if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
             Attribute::Symbol symbol;
             symbol.value = util::deviceToHost32(mapEntry.value.data);
             if (util::deviceToHost32(mapEntry.name.ident) == 0) {
@@ -799,15 +816,57 @@
         }
     }
 
-    // TODO(adamlesinski): Find min, max, i80n, etc attributes.
+    // TODO(adamlesinski): Find i80n, attributes.
     return attr;
 }
 
+static bool isMetaDataEntry(const ResTable_map& mapEntry) {
+    switch (util::deviceToHost32(mapEntry.name.ident)) {
+    case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+    case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+    case ExtendedResTableMapTypes::ATTR_COMMENT:
+        return true;
+    }
+    return false;
+}
+
+bool BinaryResourceParser::collectMetaData(const ResTable_map& mapEntry, Value* value) {
+    switch (util::deviceToHost32(mapEntry.name.ident)) {
+    case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+        value->setSource(Source(util::getString8(mSourcePool,
+                                                 util::deviceToHost32(mapEntry.value.data))));
+        return true;
+        break;
+
+    case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+        value->setSource(value->getSource().withLine(util::deviceToHost32(mapEntry.value.data)));
+        return true;
+        break;
+
+    case ExtendedResTableMapTypes::ATTR_COMMENT:
+        value->setComment(util::getString(mSourcePool, util::deviceToHost32(mapEntry.value.data)));
+        return true;
+        break;
+    }
+    return false;
+}
+
 std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
                                                         const ConfigDescription& config,
                                                         const ResTable_map_entry* map) {
     std::unique_ptr<Array> array = util::make_unique<Array>();
+    Source source;
     for (const ResTable_map& mapEntry : map) {
+        if (isMetaDataEntry(mapEntry)) {
+            if (array->items.empty()) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "out-of-sequence meta data in array");
+                return {};
+            }
+            collectMetaData(mapEntry, array->items.back().get());
+            continue;
+        }
+
         array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
     }
     return array;
@@ -818,6 +877,16 @@
                                                                 const ResTable_map_entry* map) {
     std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
     for (const ResTable_map& mapEntry : map) {
+        if (isMetaDataEntry(mapEntry)) {
+            if (styleable->entries.empty()) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "out-of-sequence meta data in styleable");
+                return {};
+            }
+            collectMetaData(mapEntry, &styleable->entries.back());
+            continue;
+        }
+
         if (util::deviceToHost32(mapEntry.name.ident) == 0) {
             // The map entry's key (attribute) is not set. This must be
             // a symbol reference, so resolve it.
@@ -841,29 +910,42 @@
                                                           const ConfigDescription& config,
                                                           const ResTable_map_entry* map) {
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+    Item* lastEntry = nullptr;
     for (const ResTable_map& mapEntry : map) {
+        if (isMetaDataEntry(mapEntry)) {
+            if (!lastEntry) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "out-of-sequence meta data in plural");
+                return {};
+            }
+            collectMetaData(mapEntry, lastEntry);
+            continue;
+        }
+
         std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
         if (!item) {
             return {};
         }
 
+        lastEntry = item.get();
+
         switch (util::deviceToHost32(mapEntry.name.ident)) {
-            case android::ResTable_map::ATTR_ZERO:
+            case ResTable_map::ATTR_ZERO:
                 plural->values[Plural::Zero] = std::move(item);
                 break;
-            case android::ResTable_map::ATTR_ONE:
+            case ResTable_map::ATTR_ONE:
                 plural->values[Plural::One] = std::move(item);
                 break;
-            case android::ResTable_map::ATTR_TWO:
+            case ResTable_map::ATTR_TWO:
                 plural->values[Plural::Two] = std::move(item);
                 break;
-            case android::ResTable_map::ATTR_FEW:
+            case ResTable_map::ATTR_FEW:
                 plural->values[Plural::Few] = std::move(item);
                 break;
-            case android::ResTable_map::ATTR_MANY:
+            case ResTable_map::ATTR_MANY:
                 plural->values[Plural::Many] = std::move(item);
                 break;
-            case android::ResTable_map::ATTR_OTHER:
+            case ResTable_map::ATTR_OTHER:
                 plural->values[Plural::Other] = std::move(item);
                 break;
         }
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 73fcf52..0745a59 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -91,6 +91,13 @@
                                               const ConfigDescription& config,
                                               const android::ResTable_map_entry* map);
 
+    /**
+     * If the mapEntry is a special type that denotes meta data (source, comment), then it is
+     * read and added to the Value.
+     * Returns true if the mapEntry was meta data.
+     */
+    bool collectMetaData(const android::ResTable_map& mapEntry, Value* value);
+
     IAaptContext* mContext;
     ResourceTable* mTable;
 
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 1f7d5ce..aa409ea 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -275,6 +275,29 @@
     return Maybe<T>();
 }
 
+/**
+ * Define the == operator between Maybe<T> and Maybe<U> if the operator T == U is defined.
+ * Otherwise this won't be defined and the compiler will yell at the callsite instead of inside
+ * Maybe.h.
+ */
+template <typename T, typename U>
+auto operator==(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+    if (a && b) {
+        return a.value() == b.value();
+    }
+    return false;
+}
+
+/**
+ * Same as operator== but negated.
+ */
+template <typename T, typename U>
+auto operator!=(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+    return !(a == b);
+}
+
 } // namespace aapt
 
 #endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index d2c33ca..9cca40e 100644
--- a/tools/aapt2/util/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -119,4 +119,14 @@
     }
 }
 
+TEST(MaybeTest, Equality) {
+    Maybe<int> a = 1;
+    Maybe<int> b = 1;
+    Maybe<int> c;
+
+    EXPECT_EQ(a, b);
+    EXPECT_EQ(b, a);
+    EXPECT_NE(a, c);
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index a898619..0dacbd7 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -158,6 +158,15 @@
     return StringPiece16();
 }
 
+inline StringPiece getString8(const android::ResStringPool& pool, size_t idx) {
+    size_t len;
+    const char* str = pool.string8At(idx, &len);
+    if (str != nullptr) {
+        return StringPiece(str, len);
+    }
+    return StringPiece();
+}
+
 /**
  * Checks that the Java string format contains no non-positional arguments (arguments without
  * explicitly specifying an index) when there are more than one argument. This is an error
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 3c260a8..6951ede 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -298,7 +298,7 @@
 
     @Override
     public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth,
-            int maxHeight) throws RemoteException {
+            int maxHeight, float frameScale) throws RemoteException {
         // TODO Auto-generated method stub
         return null;
     }