Merge "Animate drag shadow when the drag is cancelled"
diff --git a/Android.mk b/Android.mk
index 1eff5cc..7ca04a5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -344,8 +344,6 @@
 	media/java/android/media/IMediaRouterService.aidl \
 	media/java/android/media/IMediaScannerListener.aidl \
 	media/java/android/media/IMediaScannerService.aidl \
-	media/java/android/media/IRemoteControlClient.aidl \
-	media/java/android/media/IRemoteControlDisplay.aidl \
 	media/java/android/media/IRemoteDisplayCallback.aidl \
 	media/java/android/media/IRemoteDisplayProvider.aidl \
 	media/java/android/media/IRemoteVolumeController.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 6e44d77..40908f1 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -236,6 +236,8 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/target/common/obj/framework.aidl)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/DocumentsUI_intermediates)
+$(call add-clean-step, rm -f $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IRemoteControlClient.*)
+$(call add-clean-step, rm -f $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IRemoteControlDisplay.*)
 
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7fdb972..74634a9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1915,6 +1915,19 @@
         builder.build(); // callers expect this notification to be ready to use
     }
 
+    /**
+     * @hide
+     */
+    public static void addFieldsFromContext(Context context, Notification notification) {
+        if (notification.extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO) == null) {
+            notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO,
+                    context.getApplicationInfo());
+        }
+        if (!notification.extras.containsKey(EXTRA_ORIGINATING_USERID)) {
+            notification.extras.putInt(EXTRA_ORIGINATING_USERID, context.getUserId());
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -2105,11 +2118,6 @@
         private boolean mColorUtilInited = false;
 
         /**
-         * The user that built the notification originally.
-         */
-        private int mOriginatingUserId;
-
-        /**
          * Constructs a new Builder with the defaults:
          *
 
@@ -2940,7 +2948,7 @@
             // Note: This assumes that the current user can read the profile badge of the
             // originating user.
             return mContext.getPackageManager().getUserBadgeForDensity(
-                    new UserHandle(mOriginatingUserId), 0);
+                    new UserHandle(mContext.getUserId()), 0);
         }
 
         private Bitmap getProfileBadge() {
@@ -3428,10 +3436,6 @@
                 mN.extras.putStringArray(EXTRA_PEOPLE,
                         mPersonList.toArray(new String[mPersonList.size()]));
             }
-            if (mN.topic == null) {
-                mN.topic = new Topic(TOPIC_DEFAULT, mContext.getString(
-                        R.string.default_notification_topic_label));
-            }
             return mN;
         }
 
@@ -3440,12 +3444,16 @@
             ApplicationInfo applicationInfo = n.extras.getParcelable(
                     EXTRA_BUILDER_APPLICATION_INFO);
             Context builderContext;
-            try {
-                builderContext = context.createApplicationContext(applicationInfo,
-                        Context.CONTEXT_RESTRICTED);
-            } catch (NameNotFoundException e) {
-                Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
-                builderContext = context;  // try with our context
+            if (applicationInfo != null) {
+                try {
+                    builderContext = context.createApplicationContext(applicationInfo,
+                            Context.CONTEXT_RESTRICTED);
+                } catch (NameNotFoundException e) {
+                    Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
+                    builderContext = context;  // try with our context
+                }
+            } else {
+                builderContext = context; // try with given context
             }
 
             return new Builder(builderContext, n);
@@ -3494,9 +3502,7 @@
             }
 
             // lazy stuff from mContext; see comment in Builder(Context, Notification)
-            mN.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, mContext.getApplicationInfo());
-            mOriginatingUserId = mContext.getUserId();
-            mN.extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
+            Notification.addFieldsFromContext(mContext, mN);
 
             buildUnstyled();
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 043c503..3eb3e0f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -218,6 +218,8 @@
         int[] idOut = new int[1];
         INotificationManager service = getService();
         String pkg = mContext.getPackageName();
+        // Fix the notification as best we can.
+        Notification.addFieldsFromContext(mContext, notification);
         if (notification.sound != null) {
             notification.sound = notification.sound.getCanonicalUri();
             if (StrictMode.vmFileUriExposureEnabled()) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f1a55f9..1c65c94 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4558,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 c6f8740..fc7c2b3 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -233,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/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 885255f..4a3c59b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -627,9 +627,9 @@
      * {@link #CONFIG_MCC}, {@link #CONFIG_MNC},
      * {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
      * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
-     * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT} and
-     * {@link #CONFIG_LAYOUT_DIRECTION}.  Set from the {@link android.R.attr#configChanges}
-     * attribute.
+     * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
+     * {@link #CONFIG_DENSITY}, and {@link #CONFIG_LAYOUT_DIRECTION}.
+     * Set from the {@link android.R.attr#configChanges} attribute.
      */
     public int configChanges;
 
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index d9005b2..6586426 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -31,7 +32,7 @@
 interface ILauncherApps {
     void addOnAppsChangedListener(in IOnAppsChangedListener listener);
     void removeOnAppsChangedListener(in IOnAppsChangedListener listener);
-    List<ResolveInfo> getLauncherActivities(String packageName, in UserHandle user);
+    ParceledListSlice getLauncherActivities(String packageName, in UserHandle user);
     ResolveInfo resolveActivity(in Intent intent, in UserHandle user);
     void startActivityAsUser(in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 6827d7a..a5617b4 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -41,7 +41,6 @@
     private ComponentName mComponentName;
     private ResolveInfo mResolveInfo;
     private UserHandle mUser;
-    private long mFirstInstallTime;
 
     /**
      * Create a launchable activity object for a given ResolveInfo and user.
@@ -50,14 +49,12 @@
      * @param info ResolveInfo from which to create the LauncherActivityInfo.
      * @param user The UserHandle of the profile to which this activity belongs.
      */
-    LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user,
-            long firstInstallTime) {
+    LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user) {
         this(context);
         mResolveInfo = info;
         mActivityInfo = info.activityInfo;
         mComponentName = LauncherApps.getComponentName(info);
         mUser = user;
-        mFirstInstallTime = firstInstallTime;
     }
 
     LauncherActivityInfo(Context context) {
@@ -136,7 +133,7 @@
 
     /**
      * Returns the drawable for this activity, without any badging for the profile.
-     * @param resource id of the drawable.
+     * @param iconRes id of the drawable.
      * @param density The preferred density of the icon, zero for default density. Use
      * density DPI values from {@link DisplayMetrics}.
      * @see DisplayMetrics
@@ -179,7 +176,13 @@
      * @return The time of installation of the package, in milliseconds.
      */
     public long getFirstInstallTime() {
-        return mFirstInstallTime;
+        try {
+            return mPm.getPackageInfo(mActivityInfo.packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
+        } catch (NameNotFoundException nnfe) {
+            // Sorry, can't find package
+            return 0;
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index e9ec771..6e67af4 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -142,28 +142,18 @@
      * @return List of launchable activities. Can be an empty list but will not be null.
      */
     public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
-        List<ResolveInfo> activities = null;
+        ParceledListSlice<ResolveInfo> activities = null;
         try {
             activities = mService.getLauncherActivities(packageName, user);
         } catch (RemoteException re) {
-            throw new RuntimeException("Failed to call LauncherAppsService");
+            throw new RuntimeException("Failed to call LauncherAppsService", re);
         }
         if (activities == null) {
             return Collections.EMPTY_LIST;
         }
         ArrayList<LauncherActivityInfo> lais = new ArrayList<LauncherActivityInfo>();
-        final int count = activities.size();
-        for (int i = 0; i < count; i++) {
-            ResolveInfo ri = activities.get(i);
-            long firstInstallTime = 0;
-            try {
-                firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
-                    PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
-            } catch (NameNotFoundException nnfe) {
-                // Sorry, can't find package
-            }
-            LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user,
-                    firstInstallTime);
+        for (ResolveInfo ri : activities.getList()) {
+            LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user);
             if (DEBUG) {
                 Log.v(TAG, "Returning activity for profile " + user + " : "
                         + lai.getComponentName());
@@ -189,19 +179,11 @@
         try {
             ResolveInfo ri = mService.resolveActivity(intent, user);
             if (ri != null) {
-                long firstInstallTime = 0;
-                try {
-                    firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
-                            PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
-                } catch (NameNotFoundException nnfe) {
-                    // Sorry, can't find package
-                }
-                LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user,
-                        firstInstallTime);
+                LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user);
                 return info;
             }
         } catch (RemoteException re) {
-            throw new RuntimeException("Failed to call LauncherAppsService");
+            throw new RuntimeException("Failed to call LauncherAppsService", re);
         }
         return null;
     }
@@ -256,7 +238,7 @@
         try {
             return mService.isPackageEnabled(packageName, user);
         } catch (RemoteException re) {
-            throw new RuntimeException("Failed to call LauncherAppsService");
+            throw new RuntimeException("Failed to call LauncherAppsService", re);
         }
     }
 
@@ -272,7 +254,7 @@
         try {
             return mService.isActivityEnabled(component, user);
         } catch (RemoteException re) {
-            throw new RuntimeException("Failed to call LauncherAppsService");
+            throw new RuntimeException("Failed to call LauncherAppsService", re);
         }
     }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 42fef3b..0c28008 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1729,6 +1729,8 @@
      * {@link #hasSystemFeature}: The device supports freeform window management.
      * Windows have title bars and can be moved and resized.
      */
+    // If this feature is present, you also need to set
+    // com.android.internal.R.config_freeformWindowManagement to true in your configuration overlay.
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT
             = "android.software.freeform_window_management";
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/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f78d8d8..8e318a2 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -346,7 +346,7 @@
                 long startTime = SystemClock.uptimeMillis();
                 TypedArray ar = mResources.obtainTypedArray(
                         com.android.internal.R.array.preloaded_drawables);
-                int N = preloadDrawables(runtime, ar);
+                int N = preloadDrawables(ar);
                 ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
                         + (SystemClock.uptimeMillis()-startTime) + "ms.");
@@ -354,10 +354,21 @@
                 startTime = SystemClock.uptimeMillis();
                 ar = mResources.obtainTypedArray(
                         com.android.internal.R.array.preloaded_color_state_lists);
-                N = preloadColorStateLists(runtime, ar);
+                N = preloadColorStateLists(ar);
                 ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
                         + (SystemClock.uptimeMillis()-startTime) + "ms.");
+
+                if (mResources.getBoolean(
+                        com.android.internal.R.bool.config_freeformWindowManagement)) {
+                    startTime = SystemClock.uptimeMillis();
+                    ar = mResources.obtainTypedArray(
+                            com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
+                    N = preloadDrawables(ar);
+                    ar.recycle();
+                    Log.i(TAG, "...preloaded " + N + " resource in "
+                            + (SystemClock.uptimeMillis() - startTime) + "ms.");
+                }
             }
             mResources.finishPreloading();
         } catch (RuntimeException e) {
@@ -365,7 +376,7 @@
         }
     }
 
-    private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
+    private static int preloadColorStateLists(TypedArray ar) {
         int N = ar.length();
         for (int i=0; i<N; i++) {
             int id = ar.getResourceId(i, 0);
@@ -385,7 +396,7 @@
     }
 
 
-    private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
+    private static int preloadDrawables(TypedArray ar) {
         int N = ar.length();
         for (int i=0; i<N; i++) {
             int id = ar.getResourceId(i, 0);
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 33c41ef..a6a4564 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -364,6 +364,11 @@
        <item>@color/search_url_text_material_light</item>
     </array>
 
+   <array name="preloaded_freeform_multi_window_drawables">
+      <item>@drawable/decor_maximize_button_dark</item>
+      <item>@drawable/decor_maximize_button_light</item>
+   </array>
+
     <!-- Used in LocalePicker -->
     <string-array translatable="false" name="special_locale_codes">
         <!-- http://b/17150708 - ensure that the list of languages says "Arabic"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index e376903..67933cd 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -793,6 +793,10 @@
              physical screen size has changed such as switching to an external
              display. -->
         <flag name="smallestScreenSize" value="0x0800" />
+        <!-- The display density has changed. This might be caused by the user
+             specifying a different display scale, or it might be caused by a
+             different display being activated. -->
+        <flag name="density" value="0x1000" />
         <!-- The layout direction has changed. For example going from LTR to RTL. -->
         <flag name="layoutDirection" value="0x2000" />
         <!-- The font scaling factor has changed, that is the user has
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 539baa5..d9e0472 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2377,4 +2377,11 @@
     <!-- The BT name of the keyboard packaged with the device. If this is defined, SystemUI will
          automatically try to pair with it when the device exits tablet mode. -->
     <string translatable="false" name="config_packagedKeyboardName"></string>
+
+    <!-- The device supports freeform window management. Windows have title bars and can be moved
+         and resized. If you set this to true, you also need to add
+         PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT feature to your device specification.
+         The duplication is necessary, because this information is used before the features are
+         available to the system.-->
+    <bool name="config_freeformWindowManagement">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476b879..c66dd18 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -311,6 +311,7 @@
   <java-symbol type="bool" name="config_wifi_enable_wifi_firmware_debugging" />
   <java-symbol type="bool" name="config_supportMicNearUltrasound" />
   <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
+  <java-symbol type="bool" name="config_freeformWindowManagement" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" />
@@ -1123,6 +1124,7 @@
   <java-symbol type="array" name="networkAttributes" />
   <java-symbol type="array" name="preloaded_color_state_lists" />
   <java-symbol type="array" name="preloaded_drawables" />
+  <java-symbol type="array" name="preloaded_freeform_multi_window_drawables" />
   <java-symbol type="array" name="sim_colors" />
   <java-symbol type="array" name="special_locale_codes" />
   <java-symbol type="array" name="special_locale_names" />
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 89cadea..ca85dfb 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -237,7 +237,7 @@
     if (CC_LIKELY(mSwapHistory.size())) {
         nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
         const SwapHistory& lastSwap = mSwapHistory.back();
-        int vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
+        nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
         // The slight fudge-factor is to deal with cases where
         // the vsync was estimated due to being slow handling the signal.
         // See the logic in TimeLord#computeFrameTimeNanos or in
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 50df556..c658675 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2658,107 +2658,6 @@
         rctlr.stopListeningToSessions();
     }
 
-    /**
-     * @hide
-     * Registers a remote control display that will be sent information by remote control clients.
-     * Use this method if your IRemoteControlDisplay is not going to display artwork, otherwise
-     * use {@link #registerRemoteControlDisplay(IRemoteControlDisplay, int, int)} to pass the
-     * artwork size directly, or
-     * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork
-     * is not yet needed.
-     * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
-     * @param rcd the IRemoteControlDisplay
-     */
-    public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
-        // passing a negative value for art work width and height as they are unknown at this stage
-        registerRemoteControlDisplay(rcd, /*w*/-1, /*h*/ -1);
-    }
-
-    /**
-     * @hide
-     * Registers a remote control display that will be sent information by remote control clients.
-     * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
-     * @param rcd
-     * @param w the maximum width of the expected bitmap. Negative values indicate it is
-     *   useless to send artwork.
-     * @param h the maximum height of the expected bitmap. Negative values indicate it is
-     *   useless to send artwork.
-     */
-    public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
-        if (rcd == null) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            service.registerRemoteControlDisplay(rcd, w, h);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
-        }
-    }
-
-    /**
-     * @hide
-     * Unregisters a remote control display that was sent information by remote control clients.
-     * @param rcd
-     */
-    public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
-        if (rcd == null) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            service.unregisterRemoteControlDisplay(rcd);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e);
-        }
-    }
-
-    /**
-     * @hide
-     * Sets the artwork size a remote control display expects when receiving bitmaps.
-     * @param rcd
-     * @param w the maximum width of the expected bitmap. Negative values indicate it is
-     *   useless to send artwork.
-     * @param h the maximum height of the expected bitmap. Negative values indicate it is
-     *   useless to send artwork.
-     */
-    public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
-        if (rcd == null) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            service.remoteControlDisplayUsesBitmapSize(rcd, w, h);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in remoteControlDisplayUsesBitmapSize " + e);
-        }
-    }
-
-    /**
-     * @hide
-     * Controls whether a remote control display needs periodic checks of the RemoteControlClient
-     * playback position to verify that the estimated position has not drifted from the actual
-     * position. By default the check is not performed.
-     * The IRemoteControlDisplay must have been previously registered for this to have any effect.
-     * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
-     *     or disabled. No effect is null.
-     * @param wantsSync if true, RemoteControlClient instances which expose their playback position
-     *     to the framework will regularly compare the estimated playback position with the actual
-     *     position, and will update the IRemoteControlDisplay implementation whenever a drift is
-     *     detected.
-     */
-    public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
-            boolean wantsSync) {
-        if (rcd == null) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            service.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in remoteControlDisplayWantsPlaybackPositionSync " + e);
-        }
-    }
 
     /**
      * @hide
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8aebe11..693a519 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -23,9 +23,6 @@
 import android.media.AudioRoutesInfo;
 import android.media.IAudioFocusDispatcher;
 import android.media.IAudioRoutesObserver;
-import android.media.IRemoteControlClient;
-import android.media.IRemoteControlDisplay;
-import android.media.IRemoteVolumeObserver;
 import android.media.IRingtonePlayer;
 import android.media.IVolumeController;
 import android.media.Rating;
@@ -47,8 +44,6 @@
 
     void setStreamVolume(int streamType, int index, int flags, String callingPackage);
 
-    oneway void setRemoteStreamVolume(int index);
-
     boolean isStreamMute(int streamType);
 
     void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb);
@@ -121,59 +116,6 @@
 
     int getCurrentAudioFocus();
 
-    /**
-     * Register an IRemoteControlDisplay.
-     * Success of registration is subject to a check on
-     *   the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission.
-     * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
-     * at the top of the stack to update the new display with its information.
-     * @param rcd the IRemoteControlDisplay to register. No effect if null.
-     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     */
-    boolean registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
-
-    /**
-     * Like registerRemoteControlDisplay, but with success being subject to a check on
-     *   the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission, and if it fails,
-     *   success is subject to listenerComp being one of the ENABLED_NOTIFICATION_LISTENERS
-     *   components.
-     */
-    boolean registerRemoteController(in IRemoteControlDisplay rcd, int w, int h,
-            in ComponentName listenerComp);
-
-    /**
-     * Unregister an IRemoteControlDisplay.
-     * No effect if the IRemoteControlDisplay hasn't been successfully registered.
-     * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
-     */
-    oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
-    /**
-     * Update the size of the artwork used by an IRemoteControlDisplay.
-     * @param rcd the IRemoteControlDisplay with the new artwork size requirement
-     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     */
-    oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
-    /**
-     * Controls whether a remote control display needs periodic checks of the RemoteControlClient
-     * playback position to verify that the estimated position has not drifted from the actual
-     * position. By default the check is not performed.
-     * The IRemoteControlDisplay must have been previously registered for this to have any effect.
-     * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
-     *     or disabled. Not null.
-     * @param wantsSync if true, RemoteControlClient instances which expose their playback position
-     *     to the framework will regularly compare the estimated playback position with the actual
-     *     position, and will update the IRemoteControlDisplay implementation whenever a drift is
-     *     detected.
-     */
-    oneway void remoteControlDisplayWantsPlaybackPositionSync(in IRemoteControlDisplay rcd,
-            boolean wantsSync);
-
     void startBluetoothSco(IBinder cb, int targetSdkVersion);
     void startBluetoothScoVirtualCall(IBinder cb);
     void stopBluetoothSco(IBinder cb);
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
deleted file mode 100644
index aa142d6..0000000
--- a/media/java/android/media/IRemoteControlClient.aidl
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.graphics.Bitmap;
-import android.media.IRemoteControlDisplay;
-import android.media.Rating;
-
-/**
- * @hide
- * Interface registered by AudioManager to notify a source of remote control information
- * that information is requested to be displayed on the remote control (through
- * IRemoteControlDisplay).
- * {@see AudioManager#registerRemoteControlClient(RemoteControlClient)}.
- */
-oneway interface IRemoteControlClient
-{
-    /**
-     * Notifies a remote control client that information for the given generation ID is
-     * requested. If the flags contains
-     * {@link RemoteControlClient#FLAG_INFORMATION_REQUESTED_ALBUM_ART} then the width and height
-     *   parameters are valid.
-     * @param generationId
-     * @param infoFlags
-     * FIXME: is infoFlags required? since the RCC pushes info, this might always be called
-     *        with RC_INFO_ALL
-     */
-    void onInformationRequested(int generationId, int infoFlags);
-
-    /**
-     * Notifies a remote control client that information for the given generation ID is
-     * requested for the given IRemoteControlDisplay alone.
-     * @param rcd the display to which current info should be sent
-     */
-    void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h);
-
-    /**
-     * Sets the generation counter of the current client that is displayed on the remote control.
-     */
-    void setCurrentClientGenerationId(int clientGeneration);
-
-    void   plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h);
-    void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
-    void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h);
-    void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync);
-    void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled);
-    void seekTo(int clientGeneration, long timeMs);
-    void updateMetadata(int clientGeneration, int key, in Rating value);
-}
\ No newline at end of file
diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl
deleted file mode 100644
index 1609030..0000000
--- a/media/java/android/media/IRemoteControlDisplay.aidl
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-
-/**
- * @hide
- * Interface registered through AudioManager of an object that displays information
- * received from a remote control client.
- * {@see AudioManager#registerRemoteControlDisplay(IRemoteControlDisplay)}.
- */
-oneway interface IRemoteControlDisplay
-{
-    /**
-     * Sets the generation counter of the current client that is displayed on the remote control.
-     * @param clientGeneration the new RemoteControlClient generation
-     * @param clientMediaIntent the PendingIntent associated with the client.
-     *    May be null, which implies there is no registered media button event receiver.
-     * @param clearing true if the new client generation value maps to a remote control update
-     *    where the display should be cleared.
-     */
-    void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
-            boolean clearing);
-
-    /**
-     * Sets whether the controls of this display are enabled
-     * @param if false, the display shouldn't any commands
-     */
-    void setEnabled(boolean enabled);
-
-    /**
-     * Sets the playback information (state, position and speed) of a client.
-     * @param generationId the current generation ID as known by this client
-     * @param state the current playback state, one of the following values:
-     *       {@link RemoteControlClient#PLAYSTATE_STOPPED},
-     *       {@link RemoteControlClient#PLAYSTATE_PAUSED},
-     *       {@link RemoteControlClient#PLAYSTATE_PLAYING},
-     *       {@link RemoteControlClient#PLAYSTATE_FAST_FORWARDING},
-     *       {@link RemoteControlClient#PLAYSTATE_REWINDING},
-     *       {@link RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS},
-     *       {@link RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS},
-     *       {@link RemoteControlClient#PLAYSTATE_BUFFERING},
-     *       {@link RemoteControlClient#PLAYSTATE_ERROR}.
-     * @param stateChangeTimeMs the time at which the client reported the playback information
-     * @param currentPosMs a 0 or positive value for the current media position expressed in ms
-     *    Strictly negative values imply that position is not known:
-     *    a value of {@link RemoteControlClient#PLAYBACK_POSITION_INVALID} is intended to express
-     *    that an application doesn't know the position (e.g. listening to a live stream of a radio)
-     *    or that the position information is not applicable (e.g. when state
-     *    is {@link RemoteControlClient#PLAYSTATE_BUFFERING} and nothing had played yet);
-     *    a value of {@link RemoteControlClient#PLAYBACK_POSITION_ALWAYS_UNKNOWN} implies that the
-     *    application uses {@link RemoteControlClient#setPlaybackState(int)} (legacy API) and will
-     *    never pass a playback position.
-     * @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
-     *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
-     *    playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}).
-     */
-    void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs,
-            float speed);
-
-    /**
-     * Sets the transport control flags and playback position capabilities of a client.
-     * @param generationId the current generation ID as known by this client
-     * @param transportControlFlags bitmask of the transport controls this client supports, see
-     *         {@link RemoteControlClient#setTransportControlFlags(int)}
-     * @param posCapabilities a bit mask for playback position capabilities, see
-     *         {@link RemoteControlClient#MEDIA_POSITION_READABLE} and
-     *         {@link RemoteControlClient#MEDIA_POSITION_WRITABLE}
-     */
-    void setTransportControlInfo(int generationId, int transportControlFlags, int posCapabilities);
-
-    void setMetadata(int generationId, in Bundle metadata);
-
-    void setArtwork(int generationId, in Bitmap artwork);
-
-    /**
-     * To combine metadata text and artwork in one binder call
-     */
-    void setAllMetadata(int generationId, in Bundle metadata, in Bitmap artwork);
-}
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index c9a86d8..6d32eff 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -349,16 +349,6 @@
      */
     public RemoteControlClient(PendingIntent mediaButtonIntent) {
         mRcMediaIntent = mediaButtonIntent;
-
-        Looper looper;
-        if ((looper = Looper.myLooper()) != null) {
-            mEventHandler = new EventHandler(this, looper);
-        } else if ((looper = Looper.getMainLooper()) != null) {
-            mEventHandler = new EventHandler(this, looper);
-        } else {
-            mEventHandler = null;
-            Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
-        }
     }
 
     /**
@@ -378,8 +368,6 @@
      */
     public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
         mRcMediaIntent = mediaButtonIntent;
-
-        mEventHandler = new EventHandler(this, looper);
     }
 
     /**
@@ -707,39 +695,6 @@
         }
     }
 
-    // TODO investigate if we still need position drift checking
-    private void onPositionDriftCheck() {
-        if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
-        synchronized(mCacheLock) {
-            if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) {
-                return;
-            }
-            if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) {
-                if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); }
-                return;
-            }
-            long estPos = mPlaybackPositionMs + (long)
-                    ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed);
-            long actPos = mPositionProvider.onGetPlaybackPosition();
-            if (actPos >= 0) {
-                if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) {
-                    // drift happened, report the new position
-                    if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +"  est=" +estPos); }
-                    setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed);
-                } else {
-                    if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +"  est=" + estPos); }
-                    // no drift, schedule the next drift check
-                    mEventHandler.sendMessageDelayed(
-                            mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
-                            getCheckPeriodFromSpeed(mPlaybackSpeed));
-                }
-            } else {
-                // invalid position (negative value), can't check for drift
-                mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
-            }
-        }
-    }
-
     /**
      * Sets the flags for the media transport control buttons that this client supports.
      * @param transportControlFlags A combination of the following flags:
@@ -856,14 +811,6 @@
     public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) {
         synchronized(mCacheLock) {
             mPositionProvider = l;
-            if ((mPositionProvider != null) && (mEventHandler != null)
-                    && playbackPositionShouldMove(mPlaybackState)) {
-                // playback position is already moving, but now we have a position provider,
-                // so schedule a drift check right now
-                mEventHandler.sendMessageDelayed(
-                        mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
-                        0 /*check now*/);
-            }
         }
     }
 
@@ -1001,26 +948,6 @@
         }
     };
 
-    private EventHandler mEventHandler;
-    private final static int MSG_POSITION_DRIFT_CHECK = 11;
-
-    private class EventHandler extends Handler {
-        public EventHandler(RemoteControlClient rcc, Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case MSG_POSITION_DRIFT_CHECK:
-                    onPositionDriftCheck();
-                    break;
-                default:
-                    Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
-            }
-        }
-    }
-
     //===========================================================
     // Message handlers
 
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index d84cf30..90f2163 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -17,8 +17,6 @@
 package android.media;
 
 import android.app.ActivityManager;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -32,7 +30,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -61,15 +58,10 @@
 @Deprecated public final class RemoteController
 {
     private final static int MAX_BITMAP_DIMENSION = 512;
-    private final static int TRANSPORT_UNKNOWN = 0;
     private final static String TAG = "RemoteController";
     private final static boolean DEBUG = false;
-    private final static boolean USE_SESSIONS = true;
-    private final static Object mGenLock = new Object();
     private final static Object mInfoLock = new Object();
-    private final RcDisplay mRcd;
     private final Context mContext;
-    private final AudioManager mAudioManager;
     private final int mMaxBitmapDimension;
     private MetadataEditor mMetadataEditor;
 
@@ -78,15 +70,9 @@
     private MediaController.Callback mSessionCb = new MediaControllerCallback();
 
     /**
-     * Synchronized on mGenLock
-     */
-    private int mClientGenerationIdCurrent = 0;
-
-    /**
      * Synchronized on mInfoLock
      */
     private boolean mIsRegistered = false;
-    private PendingIntent mClientPendingIntentCurrent;
     private OnClientUpdateListener mOnClientUpdateListener;
     private PlaybackInfo mLastPlaybackInfo;
     private int mArtworkWidth = -1;
@@ -136,8 +122,6 @@
         }
         mOnClientUpdateListener = updateListener;
         mContext = context;
-        mRcd = new RcDisplay(this);
-        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         mSessionManager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         mSessionListener = new TopTransportSessionListener();
@@ -207,22 +191,6 @@
         public void onClientMetadataUpdate(MetadataEditor metadataEditor);
     };
 
-
-    /**
-     * @hide
-     */
-    public String getRemoteControlClientPackageName() {
-        if (USE_SESSIONS) {
-            synchronized (mInfoLock) {
-                return mCurrentSession != null ? mCurrentSession.getPackageName()
-                        : null;
-            }
-        } else {
-            return mClientPendingIntentCurrent != null ?
-                    mClientPendingIntentCurrent.getCreatorPackage() : null;
-        }
-    }
-
     /**
      * Return the estimated playback position of the current media track or a negative value
      * if not available.
@@ -240,38 +208,13 @@
      * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float)
      */
     public long getEstimatedMediaPosition() {
-        if (USE_SESSIONS) {
-            synchronized (mInfoLock) {
-                if (mCurrentSession != null) {
-                    PlaybackState state = mCurrentSession.getPlaybackState();
-                    if (state != null) {
-                        return state.getPosition();
-                    }
+        synchronized (mInfoLock) {
+            if (mCurrentSession != null) {
+                PlaybackState state = mCurrentSession.getPlaybackState();
+                if (state != null) {
+                    return state.getPosition();
                 }
             }
-        } else {
-            final PlaybackInfo lastPlaybackInfo;
-            synchronized (mInfoLock) {
-                lastPlaybackInfo = mLastPlaybackInfo;
-            }
-            if (lastPlaybackInfo != null) {
-                if (!RemoteControlClient.playbackPositionShouldMove(lastPlaybackInfo.mState)) {
-                    return lastPlaybackInfo.mCurrentPosMs;
-                }
-
-                // Take the current position at the time of state change and
-                // estimate.
-                final long thenPos = lastPlaybackInfo.mCurrentPosMs;
-                if (thenPos < 0) {
-                    return -1;
-                }
-
-                final long now = SystemClock.elapsedRealtime();
-                final long then = lastPlaybackInfo.mStateChangeTimeMs;
-                final long sinceThen = now - then;
-                final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed);
-                return thenPos + scaledSinceThen;
-            }
         }
         return -1;
     }
@@ -308,42 +251,12 @@
         if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
             throw new IllegalArgumentException("not a media key event");
         }
-        if (USE_SESSIONS) {
-            synchronized (mInfoLock) {
-                if (mCurrentSession != null) {
-                    return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
-                }
-                return false;
+        synchronized (mInfoLock) {
+            if (mCurrentSession != null) {
+                return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
             }
-        } else {
-            final PendingIntent pi;
-            synchronized (mInfoLock) {
-                if (!mIsRegistered) {
-                    Log.e(TAG,
-                            "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
-                    return false;
-                }
-                if (!mEnabled) {
-                    Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController");
-                    return false;
-                }
-                pi = mClientPendingIntentCurrent;
-            }
-            if (pi != null) {
-                Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-                intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-                try {
-                    pi.send(mContext, 0, intent);
-                } catch (CanceledException e) {
-                    Log.e(TAG, "Error sending intent for media button down: ", e);
-                    return false;
-                }
-            } else {
-                Log.i(TAG, "No-op when sending key click, no receiver right now");
-                return false;
-            }
+            return false;
         }
-        return true;
     }
 
 
@@ -453,8 +366,7 @@
             Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
             return false;
         }
-        mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
-                POSITION_SYNCHRONIZATION_CHECK == sync);
+        // deprecated, no-op
         return true;
     }
 
@@ -541,154 +453,6 @@
 
     }
 
-
-    //==================================================
-    // Implementation of IRemoteControlDisplay interface
-    private static class RcDisplay extends IRemoteControlDisplay.Stub {
-        private final WeakReference<RemoteController> mController;
-
-        RcDisplay(RemoteController rc) {
-            mController = new WeakReference<RemoteController>(rc);
-        }
-
-        public void setCurrentClientId(int genId, PendingIntent clientMediaIntent,
-                boolean clearing) {
-            final RemoteController rc = mController.get();
-            if (rc == null) {
-                return;
-            }
-            boolean isNew = false;
-            synchronized(mGenLock) {
-                if (rc.mClientGenerationIdCurrent != genId) {
-                    rc.mClientGenerationIdCurrent = genId;
-                    isNew = true;
-                }
-            }
-            if (clientMediaIntent != null) {
-                sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE,
-                        genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/);
-            }
-            if (isNew || clearing) {
-                sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
-                        genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/);
-            }
-        }
-
-        public void setEnabled(boolean enabled) {
-            final RemoteController rc = mController.get();
-            if (rc == null) {
-                return;
-            }
-            sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE,
-                    enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/);
-        }
-
-        public void setPlaybackState(int genId, int state,
-                long stateChangeTimeMs, long currentPosMs, float speed) {
-            final RemoteController rc = mController.get();
-            if (rc == null) {
-                return;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "> new playback state: genId="+genId
-                        + " state="+ state
-                        + " changeTime="+ stateChangeTimeMs
-                        + " pos=" + currentPosMs
-                        + "ms speed=" + speed);
-            }
-
-            synchronized(mGenLock) {
-                if (rc.mClientGenerationIdCurrent != genId) {
-                    return;
-                }
-            }
-            final PlaybackInfo playbackInfo =
-                    new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed);
-            sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
-                    genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/);
-
-        }
-
-        public void setTransportControlInfo(int genId, int transportControlFlags,
-                int posCapabilities) {
-            final RemoteController rc = mController.get();
-            if (rc == null) {
-                return;
-            }
-            synchronized(mGenLock) {
-                if (rc.mClientGenerationIdCurrent != genId) {
-                    return;
-                }
-            }
-            sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
-                    genId /*arg1*/, transportControlFlags /*arg2*/,
-                    null /*obj*/, 0 /*delay*/);
-        }
-
-        public void setMetadata(int genId, Bundle metadata) {
-            final RemoteController rc = mController.get();
-            if (rc == null) {
-                return;
-            }
-            if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); }
-            if (metadata == null) {
-                return;
-            }
-            synchronized(mGenLock) {
-                if (rc.mClientGenerationIdCurrent != genId) {
-                    return;
-                }
-            }
-            sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
-                    genId /*arg1*/, 0 /*arg2*/,
-                    metadata /*obj*/, 0 /*delay*/);
-        }
-
-        public void setArtwork(int genId, Bitmap artwork) {
-            final RemoteController rc = mController.get();
-            if (rc == null) {
-                return;
-            }
-            if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); }
-            synchronized(mGenLock) {
-                if (rc.mClientGenerationIdCurrent != genId) {
-                    return;
-                }
-            }
-            Bundle metadata = new Bundle(1);
-            metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork);
-            sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
-                    genId /*arg1*/, 0 /*arg2*/,
-                    metadata /*obj*/, 0 /*delay*/);
-        }
-
-        public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) {
-            final RemoteController rc = mController.get();
-            if (rc == null) {
-                return;
-            }
-            if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); }
-            if ((metadata == null) && (artwork == null)) {
-                return;
-            }
-            synchronized(mGenLock) {
-                if (rc.mClientGenerationIdCurrent != genId) {
-                    return;
-                }
-            }
-            if (metadata == null) {
-                metadata = new Bundle(1);
-            }
-            if (artwork != null) {
-                metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
-                        artwork);
-            }
-            sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
-                    genId /*arg1*/, 0 /*arg2*/,
-                    metadata /*obj*/, 0 /*delay*/);
-        }
-    }
-
     /**
      * This receives updates when the current session changes. This is
      * registered to receive the updates on the handler thread so it can call
@@ -734,14 +498,9 @@
     //==================================================
     // Event handling
     private final EventHandler mEventHandler;
-    private final static int MSG_NEW_PENDING_INTENT = 0;
-    private final static int MSG_NEW_PLAYBACK_INFO =  1;
-    private final static int MSG_NEW_TRANSPORT_INFO = 2;
-    private final static int MSG_NEW_METADATA       = 3; // msg always has non-null obj parameter
-    private final static int MSG_CLIENT_CHANGE      = 4;
-    private final static int MSG_DISPLAY_ENABLE     = 5;
-    private final static int MSG_NEW_PLAYBACK_STATE = 6;
-    private final static int MSG_NEW_MEDIA_METADATA = 7;
+    private final static int MSG_CLIENT_CHANGE      = 0;
+    private final static int MSG_NEW_PLAYBACK_STATE = 1;
+    private final static int MSG_NEW_MEDIA_METADATA = 2;
 
     private class EventHandler extends Handler {
 
@@ -752,26 +511,10 @@
         @Override
         public void handleMessage(Message msg) {
             switch(msg.what) {
-                case MSG_NEW_PENDING_INTENT:
-                    onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj);
-                    break;
-                case MSG_NEW_PLAYBACK_INFO:
-                    onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj);
-                    break;
-                case MSG_NEW_TRANSPORT_INFO:
-                    onNewTransportInfo(msg.arg1, msg.arg2);
-                    break;
-                case MSG_NEW_METADATA:
-                    onNewMetadata(msg.arg1, (Bundle)msg.obj);
-                    break;
                 case MSG_CLIENT_CHANGE:
-                    onClientChange(msg.arg1, msg.arg2 == 1);
-                    break;
-                case MSG_DISPLAY_ENABLE:
-                    onDisplayEnable(msg.arg1 == 1);
+                    onClientChange(msg.arg2 == 1);
                     break;
                 case MSG_NEW_PLAYBACK_STATE:
-                    // same as new playback info but using new apis
                     onNewPlaybackState((PlaybackState) msg.obj);
                     break;
                 case MSG_NEW_MEDIA_METADATA:
@@ -835,100 +578,7 @@
         handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
     }
 
-    ///////////// These calls are used by the old APIs with RCC and RCD //////////////////////
-    private void onNewPendingIntent(int genId, PendingIntent pi) {
-        synchronized(mGenLock) {
-            if (mClientGenerationIdCurrent != genId) {
-                return;
-            }
-        }
-        synchronized(mInfoLock) {
-            mClientPendingIntentCurrent = pi;
-        }
-    }
-
-    private void onNewPlaybackInfo(int genId, PlaybackInfo pi) {
-        synchronized(mGenLock) {
-            if (mClientGenerationIdCurrent != genId) {
-                return;
-            }
-        }
-        final OnClientUpdateListener l;
-        synchronized(mInfoLock) {
-            l = this.mOnClientUpdateListener;
-            mLastPlaybackInfo = pi;
-        }
-        if (l != null) {
-            if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
-                l.onClientPlaybackStateUpdate(pi.mState);
-            } else {
-                l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs,
-                        pi.mSpeed);
-            }
-        }
-    }
-
-    private void onNewTransportInfo(int genId, int transportControlFlags) {
-        synchronized(mGenLock) {
-            if (mClientGenerationIdCurrent != genId) {
-                return;
-            }
-        }
-        final OnClientUpdateListener l;
-        synchronized(mInfoLock) {
-            l = mOnClientUpdateListener;
-        }
-        if (l != null) {
-            l.onClientTransportControlUpdate(transportControlFlags);
-        }
-    }
-
-    /**
-     * @param genId
-     * @param metadata guaranteed to be always non-null
-     */
-    private void onNewMetadata(int genId, Bundle metadata) {
-        synchronized(mGenLock) {
-            if (mClientGenerationIdCurrent != genId) {
-                return;
-            }
-        }
-        final OnClientUpdateListener l;
-        final MetadataEditor metadataEditor;
-        // prepare the received Bundle to be used inside a MetadataEditor
-        final long editableKeys = metadata.getLong(
-                String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0);
-        if (editableKeys != 0) {
-            metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK));
-        }
-        synchronized(mInfoLock) {
-            l = mOnClientUpdateListener;
-            if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) {
-                if (mMetadataEditor.mEditorMetadata != metadata) {
-                    // existing metadata, merge existing and new
-                    mMetadataEditor.mEditorMetadata.putAll(metadata);
-                }
-
-                mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
-                        (Bitmap)metadata.getParcelable(
-                                String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)));
-                mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK);
-            } else {
-                mMetadataEditor = new MetadataEditor(metadata, editableKeys);
-            }
-            metadataEditor = mMetadataEditor;
-        }
-        if (l != null) {
-            l.onClientMetadataUpdate(metadataEditor);
-        }
-    }
-
-    private void onClientChange(int genId, boolean clearing) {
-        synchronized(mGenLock) {
-            if (mClientGenerationIdCurrent != genId) {
-                return;
-            }
-        }
+    private void onClientChange(boolean clearing) {
         final OnClientUpdateListener l;
         synchronized(mInfoLock) {
             l = mOnClientUpdateListener;
@@ -939,39 +589,6 @@
         }
     }
 
-    private void onDisplayEnable(boolean enabled) {
-        final OnClientUpdateListener l;
-        synchronized(mInfoLock) {
-            mEnabled = enabled;
-            l = this.mOnClientUpdateListener;
-        }
-        if (!enabled) {
-            // when disabling, reset all info sent to the user
-            final int genId;
-            synchronized (mGenLock) {
-                genId = mClientGenerationIdCurrent;
-            }
-            // send "stopped" state, happened "now", playback position is 0, speed 0.0f
-            final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED,
-                    SystemClock.elapsedRealtime() /*stateChangeTimeMs*/,
-                    0 /*currentPosMs*/, 0.0f /*speed*/);
-            sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
-                    genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/);
-            // send "blank" transport control info: no controls are supported
-            sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
-                    genId /*arg1*/, 0 /*arg2, no flags*/,
-                    null /*obj, ignored*/, 0 /*delay*/);
-            // send dummy metadata with empty string for title and artist, duration of 0
-            Bundle metadata = new Bundle(3);
-            metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), "");
-            metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), "");
-            metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0);
-            sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
-                    genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/);
-        }
-    }
-
-    ///////////// These calls are used by the new APIs with Sessions //////////////////////
     private void updateController(MediaController controller) {
         if (DEBUG) {
             Log.d(TAG, "Updating controller to " + controller + " previous controller is "
@@ -983,7 +600,7 @@
                     mCurrentSession.unregisterCallback(mSessionCb);
                     mCurrentSession = null;
                     sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
-                            0 /* genId */, 1 /* clearing */, null /* obj */, 0 /* delay */);
+                            0 /* arg1 ignored */, 1 /* clearing */, null /* obj */, 0 /* delay */);
                 }
             } else if (mCurrentSession == null
                     || !controller.getSessionToken()
@@ -992,17 +609,17 @@
                     mCurrentSession.unregisterCallback(mSessionCb);
                 }
                 sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
-                        0 /* genId */, 0 /* clearing */, null /* obj */, 0 /* delay */);
+                        0 /* arg1 ignored */, 0 /* clearing */, null /* obj */, 0 /* delay */);
                 mCurrentSession = controller;
                 mCurrentSession.registerCallback(mSessionCb, mEventHandler);
 
                 PlaybackState state = controller.getPlaybackState();
                 sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE,
-                        0 /* genId */, 0, state /* obj */, 0 /* delay */);
+                        0 /* arg1 ignored */, 0 /* arg2 ignored */, state /* obj */, 0 /* delay */);
 
                 MediaMetadata metadata = controller.getMetadata();
                 sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE,
-                        0 /* arg1 */, 0 /* arg2 */, metadata /* obj */, 0 /* delay */);
+                        0 /* arg1 ignored */, 0 /* arg2 ignored*/, metadata /* obj */, 0 /*delay*/);
             }
             // else same controller, no need to update
         }
@@ -1069,38 +686,6 @@
 
     /**
      * @hide
-     * Used by AudioManager to mark this instance as registered.
-     * @param registered
-     */
-    void setIsRegistered(boolean registered) {
-        synchronized (mInfoLock) {
-            mIsRegistered = registered;
-        }
-    }
-
-    /**
-     * @hide
-     * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
-     * @return
-     */
-    RcDisplay getRcDisplay() {
-        return mRcd;
-    }
-
-    /**
-     * @hide
-     * Used by AudioManager to read the current artwork dimension
-     * @return array containing width (index 0) and height (index 1) of currently set artwork size
-     */
-    int[] getArtworkSize() {
-        synchronized (mInfoLock) {
-            int[] size = { mArtworkWidth, mArtworkHeight };
-            return size;
-        }
-    }
-
-    /**
-     * @hide
      * Used by AudioManager to access user listener receiving the client update notifications
      * @return
      */
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/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/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/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/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 713eb09..e0ff4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -331,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 {
@@ -799,12 +799,13 @@
         // 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();
@@ -880,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 03d5cf1..5026c790 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -342,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 67bca3e..723989a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2092,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/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 ac89c85..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;
@@ -11881,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(
@@ -11904,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);
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 722fc32..507d76d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3691,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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 75886aa..4f2f486 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -62,7 +62,6 @@
 import android.media.IAudioFocusDispatcher;
 import android.media.IAudioRoutesObserver;
 import android.media.IAudioService;
-import android.media.IRemoteControlDisplay;
 import android.media.IRingtonePlayer;
 import android.media.IVolumeController;
 import android.media.MediaPlayer;
@@ -667,8 +666,7 @@
         mSettingsObserver = new SettingsObserver();
         createStreamStates();
 
-        mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(),
-                mContext, mVolumeController, this);
+        mMediaFocusControl = new MediaFocusControl(mContext);
 
         readAndSetLowRamDevice();
 
@@ -5235,36 +5233,6 @@
         }
     }
 
-    //==========================================================================================
-    // RemoteControlDisplay / RemoteControlClient / Remote info
-    //==========================================================================================
-    public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
-            ComponentName listenerComp) {
-        return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp);
-    }
-
-    public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
-        return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
-    }
-
-    public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
-        mMediaFocusControl.unregisterRemoteControlDisplay(rcd);
-    }
-
-    public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
-        mMediaFocusControl.remoteControlDisplayUsesBitmapSize(rcd, w, h);
-    }
-
-    public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
-            boolean wantsSync) {
-        mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
-    }
-
-    @Override
-    public void setRemoteStreamVolume(int index) {
-        enforceVolumeController("set the remote stream volume");
-        mMediaFocusControl.setRemoteStreamVolume(index);
-    }
 
     //==========================================================================================
     // Audio Focus
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index f72b598..d44d89d 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -16,52 +16,18 @@
 
 package com.android.server.audio;
 
-import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.AppOpsManager;
-import android.app.KeyguardManager;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.PendingIntent.OnFinished;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
 import android.media.AudioAttributes;
 import android.media.AudioFocusInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.IAudioFocusDispatcher;
-import android.media.IRemoteControlClient;
-import android.media.IRemoteControlDisplay;
-import android.media.IRemoteVolumeObserver;
-import android.media.RemoteControlClient;
 import android.media.audiopolicy.IAudioPolicyCallback;
-import android.net.Uri;
 import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.IDeviceIdleController;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.speech.RecognizerIntent;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
 import android.util.Log;
-import android.util.Slog;
-import android.view.KeyEvent;
-
-import com.android.server.audio.PlayerRecord.RemotePlaybackState;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -74,329 +40,22 @@
  * @hide
  *
  */
-public class MediaFocusControl implements OnFinished {
+public class MediaFocusControl {
 
     private static final String TAG = "MediaFocusControl";
 
-    /** Debug remote control client/display feature */
-    protected static final boolean DEBUG_RC = false;
-    /** Debug volumes */
-    protected static final boolean DEBUG_VOL = false;
-
-    /** Used to alter media button redirection when the phone is ringing. */
-    private boolean mIsRinging = false;
-
-    private final PowerManager.WakeLock mMediaEventWakeLock;
-    private final MediaEventHandler mEventHandler;
     private final Context mContext;
-    private final ContentResolver mContentResolver;
-    private final AudioService.VolumeController mVolumeController;
     private final AppOpsManager mAppOps;
-    private final KeyguardManager mKeyguardManager;
-    private final AudioService mAudioService;
-    private final NotificationListenerObserver mNotifListenerObserver;
 
-    protected MediaFocusControl(Looper looper, Context cntxt,
-            AudioService.VolumeController volumeCtrl, AudioService as) {
-        mEventHandler = new MediaEventHandler(looper);
+    protected MediaFocusControl(Context cntxt) {
         mContext = cntxt;
-        mContentResolver = mContext.getContentResolver();
-        mVolumeController = volumeCtrl;
-        mAudioService = as;
-
-        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
-        mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
-        int maxMusicLevel = as.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        mMainRemote = new RemotePlaybackState(-1, maxMusicLevel, maxMusicLevel);
-
-        // Register for phone state monitoring
-        TelephonyManager tmgr = (TelephonyManager)
-                mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mKeyguardManager =
-                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mNotifListenerObserver = new NotificationListenerObserver();
-
-        mHasRemotePlayback = false;
-        mMainRemoteIsActive = false;
-
-        PlayerRecord.setMediaFocusControl(this);
-
-        postReevaluateRemote();
     }
 
     protected void dump(PrintWriter pw) {
         pw.println("\nMediaFocusControl dump time: "
                 + DateFormat.getTimeInstance().format(new Date()));
         dumpFocusStack(pw);
-        dumpRCStack(pw);
-        dumpRCCStack(pw);
-        dumpRCDList(pw);
-    }
-
-    //==========================================================================================
-    // Management of RemoteControlDisplay registration permissions
-    //==========================================================================================
-    private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI =
-            Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
-    private class NotificationListenerObserver extends ContentObserver {
-
-        NotificationListenerObserver() {
-            super(mEventHandler);
-            mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) {
-                return;
-            }
-            if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); }
-            postReevaluateRemoteControlDisplays();
-        }
-    }
-
-    private final static int RCD_REG_FAILURE = 0;
-    private final static int RCD_REG_SUCCESS_PERMISSION = 1;
-    private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2;
-
-    /**
-     * Checks a caller's authorization to register an IRemoteControlDisplay.
-     * Authorization is granted if one of the following is true:
-     * <ul>
-     * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li>
-     * <li>the caller's listener is one of the enabled notification listeners</li>
-     * </ul>
-     * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay
-     *     registration.
-     */
-    private int checkRcdRegistrationAuthorization(ComponentName listenerComp) {
-        // MEDIA_CONTENT_CONTROL permission check
-        if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
-            if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");}
-            return RCD_REG_SUCCESS_PERMISSION;
-        }
-
-        // ENABLED_NOTIFICATION_LISTENERS settings check
-        if (listenerComp != null) {
-            // this call is coming from an app, can't use its identity to read secure settings
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                final int currentUser = ActivityManager.getCurrentUser();
-                final String enabledNotifListeners = Settings.Secure.getStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                        currentUser);
-                if (enabledNotifListeners != null) {
-                    final String[] components = enabledNotifListeners.split(":");
-                    for (int i=0; i<components.length; i++) {
-                        final ComponentName component =
-                                ComponentName.unflattenFromString(components[i]);
-                        if (component != null) {
-                            if (listenerComp.equals(component)) {
-                                if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component +
-                                        " is authorized notification listener"); }
-                                return RCD_REG_SUCCESS_ENABLED_NOTIF;
-                            }
-                        }
-                    }
-                }
-                if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp +
-                        " is not in list of ENABLED_NOTIFICATION_LISTENERS"); }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        return RCD_REG_FAILURE;
-    }
-
-    protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
-            ComponentName listenerComp) {
-        int reg = checkRcdRegistrationAuthorization(listenerComp);
-        if (reg != RCD_REG_FAILURE) {
-            registerRemoteControlDisplay_int(rcd, w, h, listenerComp);
-            return true;
-        } else {
-            Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
-                    ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
-                    " or be an enabled NotificationListenerService for registerRemoteController");
-            return false;
-        }
-    }
-
-    protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
-        int reg = checkRcdRegistrationAuthorization(null);
-        if (reg != RCD_REG_FAILURE) {
-            registerRemoteControlDisplay_int(rcd, w, h, null);
-            return true;
-        } else {
-            Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
-                    ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
-                    " to register IRemoteControlDisplay");
-            return false;
-        }
-    }
-
-    private void postReevaluateRemoteControlDisplays() {
-        sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
-    }
-
-    private void onReevaluateRemoteControlDisplays() {
-        if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); }
-        // read which components are enabled notification listeners
-        final int currentUser = ActivityManager.getCurrentUser();
-        final String enabledNotifListeners = Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                currentUser);
-        if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
-        synchronized(mAudioFocusLock) {
-            synchronized(mPRStack) {
-                // check whether the "enable" status of each RCD with a notification listener
-                // has changed
-                final String[] enabledComponents;
-                if (enabledNotifListeners == null) {
-                    enabledComponents = null;
-                } else {
-                    enabledComponents = enabledNotifListeners.split(":");
-                }
-                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-                while (displayIterator.hasNext()) {
-                    final DisplayInfoForServer di =
-                            displayIterator.next();
-                    if (di.mClientNotifListComp != null) {
-                        boolean wasEnabled = di.mEnabled;
-                        di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
-                                enabledComponents);
-                        if (wasEnabled != di.mEnabled){
-                            try {
-                                // tell the RCD whether it's enabled
-                                di.mRcDisplay.setEnabled(di.mEnabled);
-                                // tell the RCCs about the change for this RCD
-                                enableRemoteControlDisplayForClient_syncRcStack(
-                                        di.mRcDisplay, di.mEnabled);
-                                // when enabling, refresh the information on the display
-                                if (di.mEnabled) {
-                                    sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
-                                            di.mArtworkExpectedWidth /*arg1*/,
-                                            di.mArtworkExpectedHeight/*arg2*/,
-                                            di.mRcDisplay /*obj*/, 0/*delay*/);
-                                }
-                            } catch (RemoteException e) {
-                                Log.e(TAG, "Error en/disabling RCD: ", e);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * @param comp a non-null ComponentName
-     * @param enabledArray may be null
-     * @return
-     */
-    private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) {
-        if (enabledArray == null || enabledArray.length == 0) {
-            if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); }
-            return false;
-        }
-        final String compString = comp.flattenToString();
-        for (int i=0; i<enabledArray.length; i++) {
-            if (compString.equals(enabledArray[i])) {
-                if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); }
-                return true;
-            }
-        }
-        if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); }
-        return false;
-    }
-
-    //==========================================================================================
-    // Internal event handling
-    //==========================================================================================
-
-    // event handler messages
-    private static final int MSG_RCDISPLAY_CLEAR = 1;
-    private static final int MSG_RCDISPLAY_UPDATE = 2;
-    private static final int MSG_REEVALUATE_REMOTE = 3;
-    private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4;
-    private static final int MSG_RCC_NEW_VOLUME_OBS = 5;
-    private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6;
-    private static final int MSG_RCC_SEEK_REQUEST = 7;
-    private static final int MSG_RCC_UPDATE_METADATA = 8;
-    private static final int MSG_RCDISPLAY_INIT_INFO = 9;
-    private static final int MSG_REEVALUATE_RCD = 10;
-    private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11;
-
-    // sendMsg() flags
-    /** If the msg is already queued, replace it with this one. */
-    private static final int SENDMSG_REPLACE = 0;
-    /** If the msg is already queued, ignore this one and leave the old. */
-    private static final int SENDMSG_NOOP = 1;
-    /** If the msg is already queued, queue this one and leave the old. */
-    private static final int SENDMSG_QUEUE = 2;
-
-    private static void sendMsg(Handler handler, int msg,
-            int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
-
-        if (existingMsgPolicy == SENDMSG_REPLACE) {
-            handler.removeMessages(msg);
-        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
-            return;
-        }
-
-        handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
-    }
-
-    private class MediaEventHandler extends Handler {
-        MediaEventHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case MSG_RCDISPLAY_CLEAR:
-                    onRcDisplayClear();
-                    break;
-
-                case MSG_RCDISPLAY_UPDATE:
-                    // msg.obj is guaranteed to be non null
-                    onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1);
-                    break;
-
-                case MSG_REEVALUATE_REMOTE:
-                    onReevaluateRemote();
-                    break;
-
-                case MSG_RCC_NEW_VOLUME_OBS:
-                    onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
-                            (IRemoteVolumeObserver)msg.obj /* rvo */);
-                    break;
-
-                case MSG_RCDISPLAY_INIT_INFO:
-                    // msg.obj is guaranteed to be non null
-                    onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
-                            msg.arg1/*w*/, msg.arg2/*h*/);
-                    break;
-
-                case MSG_REEVALUATE_RCD:
-                    onReevaluateRemoteControlDisplays();
-                    break;
-
-                case MSG_UNREGISTER_MEDIABUTTONINTENT:
-                    unregisterMediaButtonIntent( (PendingIntent) msg.obj );
-                    break;
-            }
-        }
     }
 
 
@@ -406,25 +65,6 @@
 
     private final static Object mAudioFocusLock = new Object();
 
-    private final static Object mRingingLock = new Object();
-
-    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onCallStateChanged(int state, String incomingNumber) {
-            if (state == TelephonyManager.CALL_STATE_RINGING) {
-                //Log.v(TAG, " CALL_STATE_RINGING");
-                synchronized(mRingingLock) {
-                    mIsRinging = true;
-                }
-            } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK)
-                    || (state == TelephonyManager.CALL_STATE_IDLE)) {
-                synchronized(mRingingLock) {
-                    mIsRinging = false;
-                }
-            }
-        }
-    };
-
     /**
      * Discard the current audio focus owner.
      * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
@@ -865,1374 +505,4 @@
         }
     }
 
-
-    //==========================================================================================
-    // RemoteControl
-    //==========================================================================================
-    /**
-     * No-op if the key code for keyEvent is not a valid media key
-     * (see {@link #isValidMediaKeyEvent(KeyEvent)})
-     * @param keyEvent the key event to send
-     */
-    protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
-        filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
-    }
-
-    /**
-     * No-op if the key code for keyEvent is not a valid media key
-     * (see {@link #isValidMediaKeyEvent(KeyEvent)})
-     * @param keyEvent the key event to send
-     */
-    protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
-        filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
-    }
-
-    private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
-        // sanity check on the incoming key event
-        if (!isValidMediaKeyEvent(keyEvent)) {
-            Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
-            return;
-        }
-        // event filtering for telephony
-        synchronized(mRingingLock) {
-            synchronized(mPRStack) {
-                if ((mMediaReceiverForCalls != null) &&
-                        (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
-                    dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
-                    return;
-                }
-            }
-        }
-        // event filtering based on voice-based interactions
-        if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
-            filterVoiceInputKeyEvent(keyEvent, needWakeLock);
-        } else {
-            dispatchMediaKeyEvent(keyEvent, needWakeLock);
-        }
-    }
-
-    /**
-     * Handles the dispatching of the media button events to the telephony package.
-     * Precondition: mMediaReceiverForCalls != null
-     * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
-     * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
-     *     is dispatched.
-     */
-    private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) {
-        Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
-        keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-        keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
-        if (needWakeLock) {
-            mMediaEventWakeLock.acquire();
-            keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
-        }
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
-                    null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    /**
-     * Handles the dispatching of the media button events to one of the registered listeners,
-     * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
-     * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
-     * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
-     *     is dispatched.
-     */
-    private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
-        if (needWakeLock) {
-            mMediaEventWakeLock.acquire();
-        }
-        Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
-        keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-        synchronized(mPRStack) {
-            if (!mPRStack.empty()) {
-                // send the intent that was registered by the client
-                try {
-                    mPRStack.peek().getMediaButtonIntent().send(mContext,
-                            needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
-                            keyIntent, this, mEventHandler);
-                } catch (CanceledException e) {
-                    Log.e(TAG, "Error sending pending intent " + mPRStack.peek());
-                    e.printStackTrace();
-                }
-            } else {
-                // legacy behavior when nobody registered their media button event receiver
-                //    through AudioManager
-                if (needWakeLock) {
-                    keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
-                }
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
-                            null, mKeyEventDone,
-                            mEventHandler, Activity.RESULT_OK, null, null);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        }
-    }
-
-    /**
-     * The different actions performed in response to a voice button key event.
-     */
-    private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
-    private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
-    private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
-
-    private final Object mVoiceEventLock = new Object();
-    private boolean mVoiceButtonDown;
-    private boolean mVoiceButtonHandled;
-
-    /**
-     * Filter key events that may be used for voice-based interactions
-     * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
-     *    media buttons that can be used to trigger voice-based interactions.
-     * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
-     *     is dispatched.
-     */
-    private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
-        if (DEBUG_RC) {
-            Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
-        }
-
-        int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
-        int keyAction = keyEvent.getAction();
-        synchronized (mVoiceEventLock) {
-            if (keyAction == KeyEvent.ACTION_DOWN) {
-                if (keyEvent.getRepeatCount() == 0) {
-                    // initial down
-                    mVoiceButtonDown = true;
-                    mVoiceButtonHandled = false;
-                } else if (mVoiceButtonDown && !mVoiceButtonHandled
-                        && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
-                    // long-press, start voice-based interactions
-                    mVoiceButtonHandled = true;
-                    voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
-                }
-            } else if (keyAction == KeyEvent.ACTION_UP) {
-                if (mVoiceButtonDown) {
-                    // voice button up
-                    mVoiceButtonDown = false;
-                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
-                        voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
-                    }
-                }
-            }
-        }//synchronized (mVoiceEventLock)
-
-        // take action after media button event filtering for voice-based interactions
-        switch (voiceButtonAction) {
-            case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
-                if (DEBUG_RC) Log.v(TAG, "   ignore key event");
-                break;
-            case VOICEBUTTON_ACTION_START_VOICE_INPUT:
-                if (DEBUG_RC) Log.v(TAG, "   start voice-based interactions");
-                // then start the voice-based interactions
-                startVoiceBasedInteractions(needWakeLock);
-                break;
-            case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
-                if (DEBUG_RC) Log.v(TAG, "   send simulated key event, wakelock=" + needWakeLock);
-                sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
-                break;
-        }
-    }
-
-    private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) {
-        // send DOWN event
-        KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
-        dispatchMediaKeyEvent(keyEvent, needWakeLock);
-        // send UP event
-        keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
-        dispatchMediaKeyEvent(keyEvent, needWakeLock);
-
-    }
-
-    private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
-        if (keyEvent == null) {
-            return false;
-        }
-        return KeyEvent.isMediaKey(keyEvent.getKeyCode());
-    }
-
-    /**
-     * Checks whether the given key code is one that can trigger the launch of voice-based
-     *   interactions.
-     * @param keyCode the key code associated with the key event
-     * @return true if the key is one of the supported voice-based interaction triggers
-     */
-    private static boolean isValidVoiceInputKeyCode(int keyCode) {
-        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Tell the system to start voice-based interactions / voice commands
-     */
-    private void startVoiceBasedInteractions(boolean needWakeLock) {
-        Intent voiceIntent = null;
-        // select which type of search to launch:
-        // - screen on and device unlocked: action is ACTION_WEB_SEARCH
-        // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
-        //    with EXTRA_SECURE set to true if the device is securely locked
-        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
-        boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
-        if (!isLocked && pm.isScreenOn()) {
-            voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
-            Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
-        } else {
-            IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
-                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-            if (dic != null) {
-                try {
-                    dic.exitIdle("voice-search");
-                } catch (RemoteException e) {
-                }
-            }
-            voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-            voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
-                    isLocked && mKeyguardManager.isKeyguardSecure());
-            Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
-        }
-        // start the search activity
-        if (needWakeLock) {
-            mMediaEventWakeLock.acquire();
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            if (voiceIntent != null) {
-                voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
-            }
-        } catch (ActivityNotFoundException e) {
-            Log.w(TAG, "No activity for search: " + e);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-            if (needWakeLock) {
-                mMediaEventWakeLock.release();
-            }
-        }
-    }
-
-    private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
-
-    // only set when wakelock was acquired, no need to check value when received
-    private static final String EXTRA_WAKELOCK_ACQUIRED =
-            "android.media.AudioService.WAKELOCK_ACQUIRED";
-
-    public void onSendFinished(PendingIntent pendingIntent, Intent intent,
-            int resultCode, String resultData, Bundle resultExtras) {
-        if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
-            mMediaEventWakeLock.release();
-        }
-    }
-
-    BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
-        public void onReceive(Context context, Intent intent) {
-            if (intent == null) {
-                return;
-            }
-            Bundle extras = intent.getExtras();
-            if (extras == null) {
-                return;
-            }
-            if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
-                mMediaEventWakeLock.release();
-            }
-        }
-    };
-
-    /**
-     * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack
-     */
-    private final Object mCurrentRcLock = new Object();
-    /**
-     * The one remote control client which will receive a request for display information.
-     * This object may be null.
-     * Access protected by mCurrentRcLock.
-     */
-    private IRemoteControlClient mCurrentRcClient = null;
-    /**
-     * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant
-     * if mCurrentRcClient is null
-     */
-    private PendingIntent mCurrentRcClientIntent = null;
-
-    private final static int RC_INFO_NONE = 0;
-    private final static int RC_INFO_ALL =
-        RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
-        RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
-        RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
-        RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
-
-    /**
-     * A monotonically increasing generation counter for mCurrentRcClient.
-     * Only accessed with a lock on mCurrentRcLock.
-     * No value wrap-around issues as we only act on equal values.
-     */
-    private int mCurrentRcClientGen = 0;
-
-
-    /**
-     * Internal cache for the playback information of the RemoteControlClient whose volume gets to
-     * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
-     * every time we need this info.
-     */
-    private RemotePlaybackState mMainRemote;
-    /**
-     * Indicates whether the "main" RemoteControlClient is considered active.
-     * Use synchronized on mMainRemote.
-     */
-    private boolean mMainRemoteIsActive;
-    /**
-     * Indicates whether there is remote playback going on. True even if there is no "active"
-     * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
-     * handles remote playback.
-     * Use synchronized on mMainRemote.
-     */
-    private boolean mHasRemotePlayback;
-
-    /**
-     * The stack of remote control event receivers.
-     * All read and write operations on mPRStack are synchronized.
-     */
-    private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>();
-
-    /**
-     * The component the telephony package can register so telephony calls have priority to
-     * handle media button events
-     */
-    private ComponentName mMediaReceiverForCalls = null;
-
-    /**
-     * Helper function:
-     * Display in the log the current entries in the remote control focus stack
-     */
-    private void dumpRCStack(PrintWriter pw) {
-        pw.println("\nRemote Control stack entries (last is top of stack):");
-        synchronized(mPRStack) {
-            Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-            while(stackIterator.hasNext()) {
-                stackIterator.next().dump(pw, true);
-            }
-        }
-    }
-
-    /**
-     * Helper function:
-     * Display in the log the current entries in the remote control stack, focusing
-     * on RemoteControlClient data
-     */
-    private void dumpRCCStack(PrintWriter pw) {
-        pw.println("\nRemote Control Client stack entries (last is top of stack):");
-        synchronized(mPRStack) {
-            Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-            while(stackIterator.hasNext()) {
-                stackIterator.next().dump(pw, false);
-            }
-            synchronized(mCurrentRcLock) {
-                pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
-            }
-        }
-        synchronized (mMainRemote) {
-            pw.println("\nRemote Volume State:");
-            pw.println("  has remote: " + mHasRemotePlayback);
-            pw.println("  is remote active: " + mMainRemoteIsActive);
-            pw.println("  rccId: " + mMainRemote.mRccId);
-            pw.println("  volume handling: "
-                    + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
-                            "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
-            pw.println("  volume: " + mMainRemote.mVolume);
-            pw.println("  volume steps: " + mMainRemote.mVolumeMax);
-        }
-    }
-
-    /**
-     * Helper function:
-     * Display in the log the current entries in the list of remote control displays
-     */
-    private void dumpRCDList(PrintWriter pw) {
-        pw.println("\nRemote Control Display list entries:");
-        synchronized(mPRStack) {
-            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForServer di = displayIterator.next();
-                pw.println("  IRCD: " + di.mRcDisplay +
-                        "  -- w:" + di.mArtworkExpectedWidth +
-                        "  -- h:" + di.mArtworkExpectedHeight +
-                        "  -- wantsPosSync:" + di.mWantsPositionSync +
-                        "  -- " + (di.mEnabled ? "enabled" : "disabled"));
-            }
-        }
-    }
-
-    /**
-     * Helper function:
-     * Push the new media button receiver "near" the top of the PlayerRecord stack.
-     * "Near the top" is defined as:
-     *   - at the top if the current PlayerRecord at the top is not playing
-     *   - below the entries at the top of the stack that correspond to the playing PlayerRecord
-     *     otherwise
-     * Called synchronized on mPRStack
-     * precondition: mediaIntent != null
-     * @return true if the top of mPRStack was changed, false otherwise
-     */
-    private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent,
-            ComponentName target, IBinder token) {
-        if (mPRStack.empty()) {
-            mPRStack.push(new PlayerRecord(mediaIntent, target, token));
-            return true;
-        } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
-            // already at top of stack
-            return false;
-        }
-        if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
-                mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
-            return false;
-        }
-        PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
-        boolean topChanged = false;
-        PlayerRecord prse = null;
-        int lastPlayingIndex = mPRStack.size();
-        int inStackIndex = -1;
-        try {
-            // go through the stack from the top to figure out who's playing, and the position
-            // of this media button receiver (note that it may not be in the stack)
-            for (int index = mPRStack.size()-1; index >= 0; index--) {
-                prse = mPRStack.elementAt(index);
-                if (prse.isPlaybackActive()) {
-                    lastPlayingIndex = index;
-                }
-                if (prse.hasMatchingMediaButtonIntent(mediaIntent)) {
-                    inStackIndex = index;
-                }
-            }
-
-            if (inStackIndex == -1) {
-                // is not in stack
-                prse = new PlayerRecord(mediaIntent, target, token);
-                // it's new so it's not playing (no RemoteControlClient to give a playstate),
-                // therefore it goes after the ones with active playback
-                mPRStack.add(lastPlayingIndex, prse);
-            } else {
-                // is in the stack
-                if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
-                    prse = mPRStack.elementAt(inStackIndex);
-                    // remove it from its old location in the stack
-                    mPRStack.removeElementAt(inStackIndex);
-                    if (prse.isPlaybackActive()) {
-                        // and put it at the top
-                        mPRStack.push(prse);
-                    } else {
-                        // and put it after the ones with active playback
-                        if (inStackIndex > lastPlayingIndex) {
-                            mPRStack.add(lastPlayingIndex, prse);
-                        } else {
-                            mPRStack.add(lastPlayingIndex - 1, prse);
-                        }
-                    }
-                }
-            }
-
-        } catch (ArrayIndexOutOfBoundsException e) {
-            // not expected to happen, indicates improper concurrent modification or bad index
-            Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
-                    + " size=" + mPRStack.size()
-                    + " accessing media button stack", e);
-        }
-
-        return (topChanged);
-    }
-
-    /**
-     * Helper function:
-     * Remove the remote control receiver from the RC focus stack.
-     * Called synchronized on mPRStack
-     * precondition: pi != null
-     */
-    private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) {
-        try {
-            for (int index = mPRStack.size()-1; index >= 0; index--) {
-                final PlayerRecord prse = mPRStack.elementAt(index);
-                if (prse.hasMatchingMediaButtonIntent(pi)) {
-                    prse.destroy();
-                    // ok to remove element while traversing the stack since we're leaving the loop
-                    mPRStack.removeElementAt(index);
-                    break;
-                }
-            }
-        } catch (ArrayIndexOutOfBoundsException e) {
-            // not expected to happen, indicates improper concurrent modification
-            Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
-        }
-    }
-
-    /**
-     * Helper function:
-     * Called synchronized on mPRStack
-     */
-    private boolean isCurrentRcController(PendingIntent pi) {
-        if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
-            return true;
-        }
-        return false;
-    }
-
-    //==========================================================================================
-    // Remote control display / client
-    //==========================================================================================
-    /**
-     * Update the remote control displays with the new "focused" client generation
-     */
-    private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
-            PendingIntent newMediaIntent, boolean clearing) {
-        synchronized(mPRStack) {
-            if (mRcDisplays.size() > 0) {
-                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-                while (displayIterator.hasNext()) {
-                    final DisplayInfoForServer di = displayIterator.next();
-                    try {
-                        di.mRcDisplay.setCurrentClientId(
-                                newClientGeneration, newMediaIntent, clearing);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
-                        di.release();
-                        displayIterator.remove();
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Update the remote control clients with the new "focused" client generation
-     */
-    private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) {
-        // (using an iterator on the stack so we can safely remove an entry if needed,
-        //  traversal order doesn't matter here as we update all entries)
-        Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-        while(stackIterator.hasNext()) {
-            PlayerRecord se = stackIterator.next();
-            if ((se != null) && (se.getRcc() != null)) {
-                try {
-                    se.getRcc().setCurrentClientGenerationId(newClientGeneration);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
-                    stackIterator.remove();
-                    se.unlinkToRcClientDeath();
-                }
-            }
-        }
-    }
-
-    /**
-     * Update the displays and clients with the new "focused" client generation and name
-     * @param newClientGeneration the new generation value matching a client update
-     * @param newMediaIntent the media button event receiver associated with the client.
-     *    May be null, which implies there is no registered media button event receiver.
-     * @param clearing true if the new client generation value maps to a remote control update
-     *    where the display should be cleared.
-     */
-    private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
-            PendingIntent newMediaIntent, boolean clearing) {
-        // send the new valid client generation ID to all displays
-        setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
-        // send the new valid client generation ID to all clients
-        setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
-    }
-
-    /**
-     * Called when processing MSG_RCDISPLAY_CLEAR event
-     */
-    private void onRcDisplayClear() {
-        if (DEBUG_RC) Log.i(TAG, "Clear remote control display");
-
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                mCurrentRcClientGen++;
-                // synchronously update the displays and clients with the new client generation
-                setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
-                        null /*newMediaIntent*/, true /*clearing*/);
-            }
-        }
-    }
-
-    /**
-     * Called when processing MSG_RCDISPLAY_UPDATE event
-     */
-    private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) {
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) {
-                    if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");
-
-                    mCurrentRcClientGen++;
-                    // synchronously update the displays and clients with
-                    //      the new client generation
-                    setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
-                            prse.getMediaButtonIntent() /*newMediaIntent*/,
-                            false /*clearing*/);
-
-                    // tell the current client that it needs to send info
-                    try {
-                        //TODO change name to informationRequestForAllDisplays()
-                        mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Current valid remote client is dead: "+e);
-                        mCurrentRcClient = null;
-                    }
-                } else {
-                    // the remote control display owner has changed between the
-                    // the message to update the display was sent, and the time it
-                    // gets to be processed (now)
-                }
-            }
-        }
-    }
-
-    /**
-     * Called when processing MSG_RCDISPLAY_INIT_INFO event
-     * Causes the current RemoteControlClient to send its info (metadata, playstate...) to
-     *   a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.
-     */
-    private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) {
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if (mCurrentRcClient != null) {
-                    if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
-                    try {
-                        // synchronously update the new RCD with the current client generation
-                        // and matching PendingIntent
-                        newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent,
-                                false);
-
-                        // tell the current RCC that it needs to send info, but only to the new RCD
-                        try {
-                            mCurrentRcClient.informationRequestForDisplay(newRcd, w, h);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Current valid remote client is dead: ", e);
-                            mCurrentRcClient = null;
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Helper function:
-     * Called synchronized on mPRStack
-     */
-    private void clearRemoteControlDisplay_syncPrs() {
-        synchronized(mCurrentRcLock) {
-            mCurrentRcClient = null;
-        }
-        // will cause onRcDisplayClear() to be called in AudioService's handler thread
-        mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
-    }
-
-    /**
-     * Helper function for code readability: only to be called from
-     *    checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for
-     *    this method.
-     * Preconditions:
-     *    - called synchronized on mPRStack
-     *    - mPRStack.isEmpty() is false
-     */
-    private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
-        PlayerRecord prse = mPRStack.peek();
-        int infoFlagsAboutToBeUsed = infoChangedFlags;
-        // this is where we enforce opt-in for information display on the remote controls
-        //   with the new AudioManager.registerRemoteControlClient() API
-        if (prse.getRcc() == null) {
-            //Log.w(TAG, "Can't update remote control display with null remote control client");
-            clearRemoteControlDisplay_syncPrs();
-            return;
-        }
-        synchronized(mCurrentRcLock) {
-            if (!prse.getRcc().equals(mCurrentRcClient)) {
-                // new RC client, assume every type of information shall be queried
-                infoFlagsAboutToBeUsed = RC_INFO_ALL;
-            }
-            mCurrentRcClient = prse.getRcc();
-            mCurrentRcClientIntent = prse.getMediaButtonIntent();
-        }
-        // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
-        mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
-                infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) );
-    }
-
-    /**
-     * Helper function:
-     * Called synchronized on mPRStack
-     * Check whether the remote control display should be updated, triggers the update if required
-     * @param infoChangedFlags the flags corresponding to the remote control client information
-     *     that has changed, if applicable (checking for the update conditions might trigger a
-     *     clear, rather than an update event).
-     */
-    private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
-        // determine whether the remote control display should be refreshed
-        // if the player record stack is empty, there is nothing to display, so clear the RC display
-        if (mPRStack.isEmpty()) {
-            clearRemoteControlDisplay_syncPrs();
-            return;
-        }
-
-        // this is where more rules for refresh go
-
-        // refresh conditions were verified: update the remote controls
-        // ok to call: synchronized on mPRStack, mPRStack is not empty
-        updateRemoteControlDisplay_syncPrs(infoChangedFlags);
-    }
-
-    /**
-     * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
-     * precondition: mediaIntent != null
-     */
-    protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver,
-            IBinder token) {
-        Log.i(TAG, "  Remote Control   registerMediaButtonIntent() for " + mediaIntent);
-
-        synchronized(mPRStack) {
-            if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) {
-                // new RC client, assume every type of information shall be queried
-                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
-            }
-        }
-    }
-
-    /**
-     * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
-     * precondition: mediaIntent != null, eventReceiver != null
-     */
-    protected void unregisterMediaButtonIntent(PendingIntent mediaIntent)
-    {
-        Log.i(TAG, "  Remote Control   unregisterMediaButtonIntent() for " + mediaIntent);
-
-        synchronized(mPRStack) {
-            boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
-            removeMediaButtonReceiver_syncPrs(mediaIntent);
-            if (topOfStackWillChange) {
-                // current RC client will change, assume every type of info needs to be queried
-                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
-            }
-        }
-    }
-
-    protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) {
-        mEventHandler.sendMessage(
-                mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
-                        mediaIntent));
-    }
-
-    /**
-     * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
-     * precondition: c != null
-     */
-    protected void registerMediaButtonEventReceiverForCalls(ComponentName c) {
-        if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
-                != PackageManager.PERMISSION_GRANTED) {
-            Log.e(TAG, "Invalid permissions to register media button receiver for calls");
-            return;
-        }
-        synchronized(mPRStack) {
-            mMediaReceiverForCalls = c;
-        }
-    }
-
-    /**
-     * see AudioManager.unregisterMediaButtonEventReceiverForCalls()
-     */
-    protected void unregisterMediaButtonEventReceiverForCalls() {
-        if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
-                != PackageManager.PERMISSION_GRANTED) {
-            Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
-            return;
-        }
-        synchronized(mPRStack) {
-            mMediaReceiverForCalls = null;
-        }
-    }
-
-    /**
-     * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
-     * @return the unique ID of the PlayerRecord associated with the RemoteControlClient
-     * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
-     *     without modifying the RC stack, but while still causing the display to refresh (will
-     *     become blank as a result of this)
-     */
-    protected int registerRemoteControlClient(PendingIntent mediaIntent,
-            IRemoteControlClient rcClient, String callingPackageName) {
-        if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
-        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
-        synchronized(mPRStack) {
-            // store the new display information
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
-                        prse.resetControllerInfoForRcc(rcClient, callingPackageName,
-                                Binder.getCallingUid());
-
-                        if (rcClient == null) {
-                            break;
-                        }
-
-                        rccId = prse.getRccId();
-
-                        // there is a new (non-null) client:
-                        //     give the new client the displays (if any)
-                        if (mRcDisplays.size() > 0) {
-                            plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc());
-                        }
-                        break;
-                    }
-                }//for
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
-            }
-
-            // if the eventReceiver is at the top of the stack
-            // then check for potential refresh of the remote controls
-            if (isCurrentRcController(mediaIntent)) {
-                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
-            }
-        }//synchronized(mPRStack)
-        return rccId;
-    }
-
-    /**
-     * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
-     * rcClient is guaranteed non-null
-     */
-    protected void unregisterRemoteControlClient(PendingIntent mediaIntent,
-            IRemoteControlClient rcClient) {
-        if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
-        synchronized(mPRStack) {
-            boolean topRccChange = false;
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    if ((prse.hasMatchingMediaButtonIntent(mediaIntent))
-                            && rcClient.equals(prse.getRcc())) {
-                        // we found the IRemoteControlClient to unregister
-                        prse.resetControllerInfoForNoRcc();
-                        topRccChange = (index == mPRStack.size()-1);
-                        // there can only be one matching RCC in the RC stack, we're done
-                        break;
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
-            }
-            if (topRccChange) {
-                // no more RCC for the RCD, check for potential refresh of the remote controls
-                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
-            }
-        }
-    }
-
-
-    /**
-     * A class to encapsulate all the information about a remote control display.
-     * After instanciation, init() must always be called before the object is added in the list
-     * of displays.
-     * Before being removed from the list of displays, release() must always be called (otherwise
-     * it will leak death handlers).
-     */
-    private class DisplayInfoForServer implements IBinder.DeathRecipient {
-        /** may never be null */
-        private final IRemoteControlDisplay mRcDisplay;
-        private final IBinder mRcDisplayBinder;
-        private int mArtworkExpectedWidth = -1;
-        private int mArtworkExpectedHeight = -1;
-        private boolean mWantsPositionSync = false;
-        private ComponentName mClientNotifListComp;
-        private boolean mEnabled = true;
-
-        public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
-            if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
-            mRcDisplay = rcd;
-            mRcDisplayBinder = rcd.asBinder();
-            mArtworkExpectedWidth = w;
-            mArtworkExpectedHeight = h;
-        }
-
-        public boolean init() {
-            try {
-                mRcDisplayBinder.linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                // remote control display is DOA, disqualify it
-                Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
-                return false;
-            }
-            return true;
-        }
-
-        public void release() {
-            try {
-                mRcDisplayBinder.unlinkToDeath(this, 0);
-            } catch (java.util.NoSuchElementException e) {
-                // not much we can do here, the display should have been unregistered anyway
-                Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
-            }
-        }
-
-        public void binderDied() {
-            synchronized(mPRStack) {
-                Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
-                // remove the display from the list
-                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-                while (displayIterator.hasNext()) {
-                    final DisplayInfoForServer di = displayIterator.next();
-                    if (di.mRcDisplay == mRcDisplay) {
-                        if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
-                        displayIterator.remove();
-                        return;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * The remote control displays.
-     * Access synchronized on mPRStack
-     */
-    private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
-
-    /**
-     * Plug each registered display into the specified client
-     * @param rcc, guaranteed non null
-     */
-    private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) {
-        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-        while (displayIterator.hasNext()) {
-            final DisplayInfoForServer di = displayIterator.next();
-            try {
-                rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
-                        di.mArtworkExpectedHeight);
-                if (di.mWantsPositionSync) {
-                    rcc.setWantsSyncForDisplay(di.mRcDisplay, true);
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
-            }
-        }
-    }
-
-    private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd,
-            boolean enabled) {
-        // let all the remote control clients know whether the given display is enabled
-        //   (so the remote control stack traversal order doesn't matter).
-        final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-        while(stackIterator.hasNext()) {
-            PlayerRecord prse = stackIterator.next();
-            if(prse.getRcc() != null) {
-                try {
-                    prse.getRcc().enableRemoteControlDisplay(rcd, enabled);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error connecting RCD to client: ", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Is the remote control display interface already registered
-     * @param rcd
-     * @return true if the IRemoteControlDisplay is already in the list of displays
-     */
-    private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
-        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-        while (displayIterator.hasNext()) {
-            final DisplayInfoForServer di = displayIterator.next();
-            if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Register an IRemoteControlDisplay.
-     * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
-     * at the top of the stack to update the new display with its information.
-     * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
-     * @param rcd the IRemoteControlDisplay to register. No effect if null.
-     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     * @param listenerComp the component for the listener interface, may be null if it's not needed
-     *   to verify it belongs to one of the enabled notification listeners
-     */
-    private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h,
-            ComponentName listenerComp) {
-        if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
-        synchronized(mAudioFocusLock) {
-            synchronized(mPRStack) {
-                if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
-                    return;
-                }
-                DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
-                di.mEnabled = true;
-                di.mClientNotifListComp = listenerComp;
-                if (!di.init()) {
-                    if (DEBUG_RC) Log.e(TAG, " error registering RCD");
-                    return;
-                }
-                // add RCD to list of displays
-                mRcDisplays.add(di);
-
-                // let all the remote control clients know there is a new display (so the remote
-                //   control stack traversal order doesn't matter).
-                Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-                while(stackIterator.hasNext()) {
-                    PlayerRecord prse = stackIterator.next();
-                    if(prse.getRcc() != null) {
-                        try {
-                            prse.getRcc().plugRemoteControlDisplay(rcd, w, h);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Error connecting RCD to client: ", e);
-                        }
-                    }
-                }
-
-                // we have a new display, of which all the clients are now aware: have it be
-                // initialized wih the current gen ID and the current client info, do not
-                // reset the information for the other (existing) displays
-                sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
-                        w /*arg1*/, h /*arg2*/,
-                        rcd /*obj*/, 0/*delay*/);
-            }
-        }
-    }
-
-    /**
-     * Unregister an IRemoteControlDisplay.
-     * No effect if the IRemoteControlDisplay hasn't been successfully registered.
-     * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
-     * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
-     */
-    protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
-        if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
-        synchronized(mPRStack) {
-            if (rcd == null) {
-                return;
-            }
-
-            boolean displayWasPluggedIn = false;
-            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext() && !displayWasPluggedIn) {
-                final DisplayInfoForServer di = displayIterator.next();
-                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                    displayWasPluggedIn = true;
-                    di.release();
-                    displayIterator.remove();
-                }
-            }
-
-            if (displayWasPluggedIn) {
-                // disconnect this remote control display from all the clients, so the remote
-                //   control stack traversal order doesn't matter
-                final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-                while(stackIterator.hasNext()) {
-                    final PlayerRecord prse = stackIterator.next();
-                    if(prse.getRcc() != null) {
-                        try {
-                            prse.getRcc().unplugRemoteControlDisplay(rcd);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Error disconnecting remote control display to client: ", e);
-                        }
-                    }
-                }
-            } else {
-                if (DEBUG_RC) Log.w(TAG, "  trying to unregister unregistered RCD");
-            }
-        }
-    }
-
-    /**
-     * Update the size of the artwork used by an IRemoteControlDisplay.
-     * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
-     * @param rcd the IRemoteControlDisplay with the new artwork size requirement
-     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
-     *   display doesn't need to receive artwork.
-     */
-    protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
-        synchronized(mPRStack) {
-            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-            boolean artworkSizeUpdate = false;
-            while (displayIterator.hasNext() && !artworkSizeUpdate) {
-                final DisplayInfoForServer di = displayIterator.next();
-                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
-                        di.mArtworkExpectedWidth = w;
-                        di.mArtworkExpectedHeight = h;
-                        artworkSizeUpdate = true;
-                    }
-                }
-            }
-            if (artworkSizeUpdate) {
-                // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
-                // stack traversal order doesn't matter
-                final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-                while(stackIterator.hasNext()) {
-                    final PlayerRecord prse = stackIterator.next();
-                    if(prse.getRcc() != null) {
-                        try {
-                            prse.getRcc().setBitmapSizeForDisplay(rcd, w, h);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Controls whether a remote control display needs periodic checks of the RemoteControlClient
-     * playback position to verify that the estimated position has not drifted from the actual
-     * position. By default the check is not performed.
-     * The IRemoteControlDisplay must have been previously registered for this to have any effect.
-     * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
-     *     or disabled. Not null.
-     * @param wantsSync if true, RemoteControlClient instances which expose their playback position
-     *     to the framework will regularly compare the estimated playback position with the actual
-     *     position, and will update the IRemoteControlDisplay implementation whenever a drift is
-     *     detected.
-     */
-    protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
-            boolean wantsSync) {
-        synchronized(mPRStack) {
-            boolean rcdRegistered = false;
-            // store the information about this display
-            // (display stack traversal order doesn't matter).
-            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForServer di = displayIterator.next();
-                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                    di.mWantsPositionSync = wantsSync;
-                    rcdRegistered = true;
-                    break;
-                }
-            }
-            if (!rcdRegistered) {
-                return;
-            }
-            // notify all current RemoteControlClients
-            // (stack traversal order doesn't matter as we notify all RCCs)
-            final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-            while (stackIterator.hasNext()) {
-                final PlayerRecord prse = stackIterator.next();
-                if (prse.getRcc() != null) {
-                    try {
-                        prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
-                    }
-                }
-            }
-        }
-    }
-
-    // handler for MSG_RCC_NEW_VOLUME_OBS
-    private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
-        synchronized(mPRStack) {
-            // The stack traversal order doesn't matter because there is only one stack entry
-            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
-            //  start iterating from the top.
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    if (prse.getRccId() == rccId) {
-                        prse.mRemoteVolumeObs = rvo;
-                        break;
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
-            }
-        }
-    }
-
-    /**
-     * Checks if a remote client is active on the supplied stream type. Update the remote stream
-     * volume state if found and playing
-     * @param streamType
-     * @return false if no remote playing is currently playing
-     */
-    protected boolean checkUpdateRemoteStateIfActive(int streamType) {
-        synchronized(mPRStack) {
-            // iterating from top of stack as active playback is more likely on entries at the top
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
-                            && isPlaystateActive(prse.mPlaybackState.mState)
-                            && (prse.mPlaybackStream == streamType)) {
-                        if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
-                                + ", vol =" + prse.mPlaybackVolume);
-                        synchronized (mMainRemote) {
-                            mMainRemote.mRccId = prse.getRccId();
-                            mMainRemote.mVolume = prse.mPlaybackVolume;
-                            mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax;
-                            mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling;
-                            mMainRemoteIsActive = true;
-                        }
-                        return true;
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
-            }
-        }
-        synchronized (mMainRemote) {
-            mMainRemoteIsActive = false;
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if the given playback state is considered "active", i.e. it describes a state
-     * where playback is happening, or about to
-     * @param playState the playback state to evaluate
-     * @return true if active, false otherwise (inactive or unknown)
-     */
-    protected static boolean isPlaystateActive(int playState) {
-        switch (playState) {
-            case RemoteControlClient.PLAYSTATE_PLAYING:
-            case RemoteControlClient.PLAYSTATE_BUFFERING:
-            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
-            case RemoteControlClient.PLAYSTATE_REWINDING:
-            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
-            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    private void sendVolumeUpdateToRemote(int rccId, int direction) {
-        if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
-        if (direction == 0) {
-            // only handling discrete events
-            return;
-        }
-        IRemoteVolumeObserver rvo = null;
-        synchronized (mPRStack) {
-            // The stack traversal order doesn't matter because there is only one stack entry
-            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
-            //  start iterating from the top.
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
-                    if (prse.getRccId() == rccId) {
-                        rvo = prse.mRemoteVolumeObs;
-                        break;
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
-            }
-        }
-        if (rvo != null) {
-            try {
-                rvo.dispatchRemoteVolumeUpdate(direction, -1);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error dispatching relative volume update", e);
-            }
-        }
-    }
-
-    protected int getRemoteStreamMaxVolume() {
-        synchronized (mMainRemote) {
-            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
-                return 0;
-            }
-            return mMainRemote.mVolumeMax;
-        }
-    }
-
-    protected int getRemoteStreamVolume() {
-        synchronized (mMainRemote) {
-            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
-                return 0;
-            }
-            return mMainRemote.mVolume;
-        }
-    }
-
-    protected void setRemoteStreamVolume(int vol) {
-        if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
-        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
-        synchronized (mMainRemote) {
-            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
-                return;
-            }
-            rccId = mMainRemote.mRccId;
-        }
-        IRemoteVolumeObserver rvo = null;
-        synchronized (mPRStack) {
-            // The stack traversal order doesn't matter because there is only one stack entry
-            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
-            //  start iterating from the top.
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
-                    if (prse.getRccId() == rccId) {
-                        rvo = prse.mRemoteVolumeObs;
-                        break;
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
-            }
-        }
-        if (rvo != null) {
-            try {
-                rvo.dispatchRemoteVolumeUpdate(0, vol);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error dispatching absolute volume update", e);
-            }
-        }
-    }
-
-    /**
-     * Call to make AudioService reevaluate whether it's in a mode where remote players should
-     * have their volume controlled. In this implementation this is only to reset whether
-     * VolumePanel should display remote volumes
-     */
-    protected void postReevaluateRemote() {
-        sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
-    }
-
-    private void onReevaluateRemote() {
-        // TODO This was used to notify VolumePanel if there was remote playback
-        // in the stack. This is now in MediaSessionService. More code should be
-        // removed.
-    }
-
 }
diff --git a/services/core/java/com/android/server/audio/PlayerRecord.java b/services/core/java/com/android/server/audio/PlayerRecord.java
deleted file mode 100644
index e98f12e..0000000
--- a/services/core/java/com/android/server/audio/PlayerRecord.java
+++ /dev/null
@@ -1,360 +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.server.audio;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.media.AudioManager;
-import android.media.IRemoteControlClient;
-import android.media.IRemoteVolumeObserver;
-import android.media.RemoteControlClient;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.PrintWriter;
-
-/**
- * @hide
- * Class to handle all the information about a media player, encapsulating information
- * about its use RemoteControlClient, playback type and volume... The lifecycle of each
- * instance is managed by android.media.MediaFocusControl, from its addition to the player stack
- * stack to its release.
- */
-class PlayerRecord implements DeathRecipient {
-
-    // on purpose not using this classe's name, as it will only be used from MediaFocusControl
-    private static final String TAG = "MediaFocusControl";
-    private static final boolean DEBUG = false;
-
-    /**
-     * A global counter for RemoteControlClient identifiers
-     */
-    private static int sLastRccId = 0;
-
-    public static MediaFocusControl sController;
-
-    /**
-     * The target for the ACTION_MEDIA_BUTTON events.
-     * Always non null. //FIXME verify
-     */
-    final private PendingIntent mMediaIntent;
-    /**
-     * The registered media button event receiver.
-     */
-    final private ComponentName mReceiverComponent;
-
-    private int mRccId = -1;
-
-    /**
-     * A non-null token implies this record tracks a "live" player whose death is being monitored.
-     */
-    private IBinder mToken;
-    private String mCallingPackageName;
-    private int mCallingUid;
-    /**
-     * Provides access to the information to display on the remote control.
-     * May be null (when a media button event receiver is registered,
-     *     but no remote control client has been registered) */
-    private IRemoteControlClient mRcClient;
-    private RcClientDeathHandler mRcClientDeathHandler;
-    /**
-     * Information only used for non-local playback
-     */
-    //FIXME private?
-    public int mPlaybackType;
-    public int mPlaybackVolume;
-    public int mPlaybackVolumeMax;
-    public int mPlaybackVolumeHandling;
-    public int mPlaybackStream;
-    public RccPlaybackState mPlaybackState;
-    public IRemoteVolumeObserver mRemoteVolumeObs;
-
-
-    protected static class RccPlaybackState {
-        public int mState;
-        public long mPositionMs;
-        public float mSpeed;
-
-        public RccPlaybackState(int state, long positionMs, float speed) {
-            mState = state;
-            mPositionMs = positionMs;
-            mSpeed = speed;
-        }
-
-        public void reset() {
-            mState = RemoteControlClient.PLAYSTATE_STOPPED;
-            mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
-            mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
-        }
-
-        @Override
-        public String toString() {
-            return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
-        }
-
-        private String posToString() {
-            if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
-                return "PLAYBACK_POSITION_INVALID";
-            } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
-                return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
-            } else {
-                return (String.valueOf(mPositionMs) + "ms");
-            }
-        }
-
-        private String stateToString() {
-            switch (mState) {
-                case RemoteControlClient.PLAYSTATE_NONE:
-                    return "PLAYSTATE_NONE";
-                case RemoteControlClient.PLAYSTATE_STOPPED:
-                    return "PLAYSTATE_STOPPED";
-                case RemoteControlClient.PLAYSTATE_PAUSED:
-                    return "PLAYSTATE_PAUSED";
-                case RemoteControlClient.PLAYSTATE_PLAYING:
-                    return "PLAYSTATE_PLAYING";
-                case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
-                    return "PLAYSTATE_FAST_FORWARDING";
-                case RemoteControlClient.PLAYSTATE_REWINDING:
-                    return "PLAYSTATE_REWINDING";
-                case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
-                    return "PLAYSTATE_SKIPPING_FORWARDS";
-                case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
-                    return "PLAYSTATE_SKIPPING_BACKWARDS";
-                case RemoteControlClient.PLAYSTATE_BUFFERING:
-                    return "PLAYSTATE_BUFFERING";
-                case RemoteControlClient.PLAYSTATE_ERROR:
-                    return "PLAYSTATE_ERROR";
-                default:
-                    return "[invalid playstate]";
-            }
-        }
-    }
-
-
-    /**
-     * Inner class to monitor remote control client deaths, and remove the client for the
-     * remote control stack if necessary.
-     */
-    private class RcClientDeathHandler implements IBinder.DeathRecipient {
-        final private IBinder mCb; // To be notified of client's death
-        //FIXME needed?
-        final private PendingIntent mMediaIntent;
-
-        RcClientDeathHandler(IBinder cb, PendingIntent pi) {
-            mCb = cb;
-            mMediaIntent = pi;
-        }
-
-        public void binderDied() {
-            Log.w(TAG, "  RemoteControlClient died");
-            // remote control client died, make sure the displays don't use it anymore
-            //  by setting its remote control client to null
-            sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
-            // the dead client was maybe handling remote playback, the controller should reevaluate
-            sController.postReevaluateRemote();
-        }
-
-        public IBinder getBinder() {
-            return mCb;
-        }
-    }
-
-
-    protected static class RemotePlaybackState {
-        int mRccId;
-        int mVolume;
-        int mVolumeMax;
-        int mVolumeHandling;
-
-        protected RemotePlaybackState(int id, int vol, int volMax) {
-            mRccId = id;
-            mVolume = vol;
-            mVolumeMax = volMax;
-            mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
-        }
-    }
-
-
-    void dump(PrintWriter pw, boolean registrationInfo) {
-        if (registrationInfo) {
-            pw.println("  pi: " + mMediaIntent +
-                    " -- pack: " + mCallingPackageName +
-                    "  -- ercvr: " + mReceiverComponent +
-                    "  -- client: " + mRcClient +
-                    "  -- uid: " + mCallingUid +
-                    "  -- type: " + mPlaybackType +
-                    "  state: " + mPlaybackState);
-        } else {
-            // emphasis on state
-            pw.println("  uid: " + mCallingUid +
-                    "  -- id: " + mRccId +
-                    "  -- type: " + mPlaybackType +
-                    "  -- state: " + mPlaybackState +
-                    "  -- vol handling: " + mPlaybackVolumeHandling +
-                    "  -- vol: " + mPlaybackVolume +
-                    "  -- volMax: " + mPlaybackVolumeMax +
-                    "  -- volObs: " + mRemoteVolumeObs);
-        }
-    }
-
-
-    static protected void setMediaFocusControl(MediaFocusControl mfc) {
-        sController = mfc;
-    }
-
-    /** precondition: mediaIntent != null */
-    protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)
-    {
-        mMediaIntent = mediaIntent;
-        mReceiverComponent = eventReceiver;
-        mToken = token;
-        mCallingUid = -1;
-        mRcClient = null;
-        mRccId = ++sLastRccId;
-        mPlaybackState = new RccPlaybackState(
-                RemoteControlClient.PLAYSTATE_STOPPED,
-                RemoteControlClient.PLAYBACK_POSITION_INVALID,
-                RemoteControlClient.PLAYBACK_SPEED_1X);
-
-        resetPlaybackInfo();
-        if (mToken != null) {
-            try {
-                mToken.linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                sController.unregisterMediaButtonIntentAsync(mMediaIntent);
-            }
-        }
-    }
-
-    //---------------------------------------------
-    // Accessors
-    protected int getRccId() {
-        return mRccId;
-    }
-
-    protected IRemoteControlClient getRcc() {
-        return mRcClient;
-    }
-
-    protected ComponentName getMediaButtonReceiver() {
-        return mReceiverComponent;
-    }
-
-    protected PendingIntent getMediaButtonIntent() {
-        return mMediaIntent;
-    }
-
-    protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
-        if (mToken != null) {
-            return mMediaIntent.equals(pi);
-        } else {
-            if (mReceiverComponent != null) {
-                return mReceiverComponent.equals(pi.getIntent().getComponent());
-            } else {
-                return false;
-            }
-        }
-    }
-
-    protected boolean isPlaybackActive() {
-        return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
-    }
-
-    //---------------------------------------------
-    // Modify the records stored in the instance
-    protected void resetControllerInfoForRcc(IRemoteControlClient rcClient,
-            String callingPackageName, int uid) {
-        // already had a remote control client?
-        if (mRcClientDeathHandler != null) {
-            // stop monitoring the old client's death
-            unlinkToRcClientDeath();
-        }
-        // save the new remote control client
-        mRcClient = rcClient;
-        mCallingPackageName = callingPackageName;
-        mCallingUid = uid;
-        if (rcClient == null) {
-            // here mcse.mRcClientDeathHandler is null;
-            resetPlaybackInfo();
-        } else {
-            IBinder b = mRcClient.asBinder();
-            RcClientDeathHandler rcdh =
-                    new RcClientDeathHandler(b, mMediaIntent);
-            try {
-                b.linkToDeath(rcdh, 0);
-            } catch (RemoteException e) {
-                // remote control client is DOA, disqualify it
-                Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
-                mRcClient = null;
-            }
-            mRcClientDeathHandler = rcdh;
-        }
-    }
-
-    protected void resetControllerInfoForNoRcc() {
-        // stop monitoring the RCC death
-        unlinkToRcClientDeath();
-        // reset the RCC-related fields
-        mRcClient = null;
-        mCallingPackageName = null;
-    }
-
-    public void resetPlaybackInfo() {
-        mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
-        mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
-        mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
-        mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
-        mPlaybackStream = AudioManager.STREAM_MUSIC;
-        mPlaybackState.reset();
-        mRemoteVolumeObs = null;
-    }
-
-    //---------------------------------------------
-    public void unlinkToRcClientDeath() {
-        if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
-            try {
-                mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
-                mRcClientDeathHandler = null;
-            } catch (java.util.NoSuchElementException e) {
-                // not much we can do here
-                Log.e(TAG, "Error in unlinkToRcClientDeath()", e);
-            }
-        }
-    }
-
-    // FIXME rename to "release"? (as in FocusRequester class)
-    public void destroy() {
-        unlinkToRcClientDeath();
-        if (mToken != null) {
-            mToken.unlinkToDeath(this, 0);
-            mToken = null;
-        }
-    }
-
-    @Override
-    public void binderDied() {
-        sController.unregisterMediaButtonIntentAsync(mMediaIntent);
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        destroy(); // unlink exception handled inside method
-        super.finalize();
-    }
-}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 4d33248..543cd89 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -226,9 +226,7 @@
             r = new Record();
             r.pkg = pkg;
             r.uid = uid;
-            r.topics.put(Notification.TOPIC_DEFAULT,
-                    new Topic(new Notification.Topic(Notification.TOPIC_DEFAULT,
-                            mContext.getString(R.string.default_notification_topic_label))));
+            r.topics.put(Notification.TOPIC_DEFAULT, new Topic(createDefaultTopic()));
             mRecords.put(key, r);
         }
         return r;
@@ -406,6 +404,9 @@
     }
 
     private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
+        if (topic == null) {
+            topic = createDefaultTopic();
+        }
         Topic t = r.topics.get(topic.getId());
         if (t != null) {
             return t;
@@ -416,6 +417,11 @@
         }
     }
 
+    private Notification.Topic createDefaultTopic() {
+        return new Notification.Topic(Notification.TOPIC_DEFAULT,
+                mContext.getString(R.string.default_notification_topic_label));
+    }
+
     public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
         if (filter == null) {
             final int N = mSignalExtractors.length;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 4582828..0796811 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -26,6 +26,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageInfo;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Rect;
@@ -187,11 +188,11 @@
         }
 
         @Override
-        public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
+        public ParceledListSlice<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
                 throws RemoteException {
             ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
             if (!isUserEnabled(user)) {
-                return new ArrayList<ResolveInfo>();
+                return null;
             }
 
             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -201,7 +202,7 @@
             try {
                 List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0 /* flags */,
                         user.getIdentifier());
-                return apps;
+                return new ParceledListSlice<>(apps);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 203c4b6..c7fccc3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -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);
@@ -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
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4b855ed..8beee73 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6841,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/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;
     }