Additional changes to improve performance when loading Recents. (Bug 16987565)

- Ensuring that we consistently load activity icon, title and color for both full and shallow stacks
- Adding activity info caches when loading the stacks
- Should not be scaling pin icon when launching from pin
- Tweaking snap-back, over scroll, and shadows

Change-Id: I556b93562bb2c69e4c25ce787a7a34532ab706ca
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 121a8bf..d646510 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -123,10 +123,10 @@
     <!-- The min animation duration for animating views that are newly visible. -->
     <integer name="recents_filter_animate_new_views_duration">250</integer>
     <!-- The min animation duration for animating the task bar in. -->
-    <integer name="recents_animate_task_bar_enter_duration">275</integer>
+    <integer name="recents_animate_task_bar_enter_duration">225</integer>
     <!-- The animation delay for animating the first task in. This should roughly be the animation
      duration of the transition in to recents. -->
-    <integer name="recents_animate_task_bar_enter_delay">300</integer>
+    <integer name="recents_animate_task_bar_enter_delay">275</integer>
     <!-- The min animation duration for animating the task bar out. -->
     <integer name="recents_animate_task_exit_to_home_duration">225</integer>
     <!-- The min animation duration for animating the task bar out. -->
@@ -143,6 +143,8 @@
     <integer name="recents_nav_bar_scrim_enter_duration">400</integer>
     <!-- The animation duration for animating the removal of a task view. -->
     <integer name="recents_animate_task_view_remove_duration">250</integer>
+    <!-- The animation duration for scrolling the stack to a particular item. -->
+    <integer name="recents_animate_task_stack_scroll_duration">225</integer>
     <!-- The minimum alpha for the dim applied to cards that go deeper into the stack. -->
     <integer name="recents_max_task_stack_view_dim">96</integer>
     <!-- The delay to enforce between each alt-tab key press. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index cc14292..8c30f22 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -202,17 +202,14 @@
     <!-- The size of the application icon in the recents task view. -->
     <dimen name="recents_task_view_application_icon_size">48dp</dimen>
 
-    <!-- The size of the activity icon in the recents task view. -->
-    <dimen name="recents_task_view_activity_icon_size">60dp</dimen>
-
     <!-- The radius of the rounded corners on a task view. -->
     <dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
 
     <!-- The min translation in the Z index for the last task. -->
-    <dimen name="recents_task_view_z_min">25dp</dimen>
+    <dimen name="recents_task_view_z_min">20dp</dimen>
 
     <!-- The max translation in the Z index for the last task. -->
-    <dimen name="recents_task_view_z_max">100dp</dimen>
+    <dimen name="recents_task_view_z_max">80dp</dimen>
 
     <!-- The amount to translate when animating the removal of a task. -->
     <dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen>
@@ -238,6 +235,9 @@
     <!-- The side padding for the task stack as a percentage of the width. -->
     <item name="recents_stack_width_padding_percentage" format="float" type="dimen">0.04444</item>
 
+    <!-- The overscroll percentage allowed on the stack. -->
+    <item name="recents_stack_overscroll_percentage" format="float" type="dimen">0.0875</item>
+
     <!-- The top offset for the task stack. -->
     <dimen name="recents_stack_top_padding">16dp</dimen>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 804766a..d236c7e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -33,8 +33,7 @@
         <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
         <item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item>
-
-        <item name="android:ambientShadowAlpha">0.30</item>
+        <item name="android:ambientShadowAlpha">0.35</item>
     </style>
 
     <!-- Animations for a non-full-screen window or activity. -->
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 0b36bdb..d328660 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -22,7 +22,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -36,7 +35,6 @@
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskGrouping;
@@ -99,6 +97,7 @@
     long mLastToggleTime;
 
     public AlternateRecentsComponent(Context context) {
+        RecentsTaskLoader.initialize(context);
         Resources res = context.getResources();
         mContext = context;
         mSystemServicesProxy = new SystemServicesProxy(context);
@@ -176,8 +175,9 @@
     }
 
     void showRelativeAffiliatedTask(boolean showNextTask) {
-        TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy,
-                Integer.MAX_VALUE, mContext.getResources());
+        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
+                -1, -1, false, null, null);
         // Return early if there are no tasks
         if (stack.getTaskCount() == 0) return;
 
@@ -385,16 +385,8 @@
                 toTask);
         if (toTransform != null && toTask.key != null) {
             Rect toTaskRect = toTransform.rect;
-            ActivityInfo info = mSystemServicesProxy.getActivityInfo(
-                    toTask.key.baseIntent.getComponent(), toTask.key.userId);
-            if (toTask.activityIcon == null) {
-                toTask.activityIcon = mSystemServicesProxy.getActivityIcon(info,
-                        toTask.key.userId);
-            }
-            if (toTask.activityLabel == null) {
-                toTask.activityLabel = mSystemServicesProxy.getActivityLabel(info);
-            }
 
+            // XXX: Reduce the memory usage the to the task bar height
             Bitmap thumbnail = Bitmap.createBitmap(toTaskRect.width(), toTaskRect.height(),
                     Bitmap.Config.ARGB_8888);
             if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
@@ -420,8 +412,9 @@
     TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome,
                                                       Task runningTaskOut) {
         // Get the stack of tasks that we are animating into
-        TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, -1,
-                mContext.getResources());
+        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
+                runningTaskId, -1, false, null, null);
         if (stack.getTaskCount() == 0) {
             return null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index a5b845d..2f9715f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -343,7 +343,6 @@
         super.onCreate(savedInstanceState);
 
         // Initialize the loader and the configuration
-        RecentsTaskLoader.initialize(this);
         mConfig = RecentsConfiguration.reinitialize(this,
                 RecentsTaskLoader.getInstance().getSystemServicesProxy());
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 9803687..5d8181c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -67,9 +67,11 @@
     public int searchBarSpaceHeightPx;
 
     /** Task stack */
+    public int taskStackScrollDuration;
     public int taskStackMaxDim;
     public int taskStackTopPaddingPx;
     public float taskStackWidthPaddingPct;
+    public float taskStackOverscrollPct;
 
     /** Task view animation and styles */
     public int taskViewEnterFromHomeDuration;
@@ -195,9 +197,14 @@
         searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
 
         // Task stack
+        taskStackScrollDuration =
+                res.getInteger(R.integer.recents_animate_task_stack_scroll_duration);
         TypedValue widthPaddingPctValue = new TypedValue();
         res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true);
         taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();
+        TypedValue stackOverscrollPctValue = new TypedValue();
+        res.getValue(R.dimen.recents_stack_overscroll_percentage, stackOverscrollPctValue, true);
+        taskStackOverscrollPct = stackOverscrollPctValue.getFloat();
         taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
         taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding);
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 31011ae..60e89bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.recents.model;
 
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Looper;
@@ -36,7 +35,7 @@
     }
 
     PackageCallbacks mCb;
-    List<ActivityManager.RecentTaskInfo> mTasks;
+    List<Task.TaskKey> mTasks;
     SystemServicesProxy mSystemServicesProxy;
 
     /** Registers the broadcast receivers with the specified callbacks. */
@@ -64,7 +63,7 @@
     }
 
     /** Sets the list of tasks to match against package broadcast changes. */
-    void setTasks(List<ActivityManager.RecentTaskInfo> tasks) {
+    void setTasks(List<Task.TaskKey> tasks) {
         mTasks = tasks;
     }
 
@@ -75,7 +74,7 @@
         // Identify all the tasks that should be removed as a result of the package being removed.
         // Using a set to ensure that we callback once per unique component.
         HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
-        for (ActivityManager.RecentTaskInfo t : mTasks) {
+        for (Task.TaskKey t : mTasks) {
             ComponentName cn = t.baseIntent.getComponent();
             if (cn.getPackageName().equals(packageName)) {
                 componentsToRemove.add(cn);
@@ -99,7 +98,7 @@
         // Using a set to ensure that we callback once per unique component.
         HashSet<ComponentName> componentsKnownToExist = new HashSet<ComponentName>();
         HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
-        for (ActivityManager.RecentTaskInfo t : mTasks) {
+        for (Task.TaskKey t : mTasks) {
             ComponentName cn = t.baseIntent.getComponent();
             if (cn.getPackageName().equals(packageName)) {
                 if (componentsKnownToExist.contains(cn)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index b93c126..a93e244 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -35,11 +35,16 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 
+/** Handle to an ActivityInfo */
+class ActivityInfoHandle {
+    ActivityInfo info;
+}
+
 /** A bitmap load queue */
 class TaskResourceLoadQueue {
     ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
@@ -230,7 +235,6 @@
     static RecentsTaskLoader sInstance;
 
     SystemServicesProxy mSystemServicesProxy;
-    DrawableLruCache mTaskDescriptionIconCache;
     DrawableLruCache mApplicationIconCache;
     BitmapLruCache mThumbnailCache;
     StringLruCache mActivityLabelCache;
@@ -274,7 +278,6 @@
         mSystemServicesProxy = new SystemServicesProxy(context);
         mPackageMonitor = new RecentsPackageMonitor();
         mLoadQueue = new TaskResourceLoadQueue();
-        mTaskDescriptionIconCache = new DrawableLruCache(iconCacheSize);
         mApplicationIconCache = new DrawableLruCache(iconCacheSize);
         mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
         mActivityLabelCache = new StringLruCache(100);
@@ -301,103 +304,151 @@
     }
 
     /** Gets the list of recent tasks, ordered from back to front. */
-    private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp,
-            int numTasks) {
-        // Set a default number of tasks to query if none is provided
-        if (numTasks < 0) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
-            numTasks = config.maxNumTasksToLoad;
-        }
+    private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp) {
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
         List<ActivityManager.RecentTaskInfo> tasks =
-                ssp.getRecentTasks(numTasks, UserHandle.CURRENT.getIdentifier());
+                ssp.getRecentTasks(config.maxNumTasksToLoad,
+                        UserHandle.CURRENT.getIdentifier());
         Collections.reverse(tasks);
         return tasks;
     }
 
+    /** Returns the activity icon using as many cached values as we can. */
+    public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
+             ActivityManager.TaskDescription td, SystemServicesProxy ssp,
+             Resources res, ActivityInfoHandle infoHandle, boolean preloadTask) {
+        // Return the cached activity icon if it exists
+        Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
+        if (icon != null) {
+            return icon;
+        }
+        // Return the task description icon if it exists
+        if (td != null && td.getIcon() != null) {
+            icon = ssp.getBadgedIcon(new BitmapDrawable(res, td.getIcon()), taskKey.userId);
+            mApplicationIconCache.put(taskKey, icon);
+            return icon;
+        }
+        // If we are preloading this task, continue to load the activity icon
+        if (preloadTask) {
+            // All short paths failed, load the icon from the activity info and cache it
+            if (infoHandle.info == null) {
+                infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
+                        taskKey.userId);
+            }
+            icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
+            mApplicationIconCache.put(taskKey, icon);
+            return icon;
+        }
+        // If we are not preloading, return the default icon to show
+        return null;
+    }
+
+    /** Returns the activity label using as many cached values as we can. */
+    public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
+            ActivityManager.TaskDescription td, SystemServicesProxy ssp,
+            ActivityInfoHandle infoHandle) {
+        // Return the task description label if it exists
+        if (td != null && td.getLabel() != null) {
+            return td.getLabel();
+        }
+        // Return the cached activity label if it exists
+        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+        // All short paths failed, load the label from the activity info and cache it
+        if (infoHandle.info == null) {
+            infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
+                    taskKey.userId);
+        }
+        label = ssp.getActivityLabel(infoHandle.info);
+        mActivityLabelCache.put(taskKey, label);
+        return label;
+    }
+
+    /** Returns the activity's primary color. */
+    public int getActivityPrimaryColor(ActivityManager.TaskDescription td,
+            RecentsConfiguration config) {
+        if (td != null && td.getPrimaryColor() != 0) {
+            return td.getPrimaryColor();
+        }
+        return config.taskBarViewDefaultBackgroundColor;
+    }
+
     /** Reload the set of recent tasks */
     public SpaceNode reload(Context context, int preloadCount) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        Resources res = context.getResources();
-        LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>();
-        ArrayList<Task> tasksToAdd = new ArrayList<Task>();
-        TaskStack stack = new TaskStack();
+        ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
+        ArrayList<Task> tasksToLoad = new ArrayList<Task>();
+        TaskStack stack = getTaskStack(mSystemServicesProxy, context.getResources(),
+                -1, preloadCount, true, taskKeys, tasksToLoad);
         SpaceNode root = new SpaceNode();
         root.setStack(stack);
 
-        // Get the recent tasks
-        SystemServicesProxy ssp = mSystemServicesProxy;
-        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp, -1);
+        // Start the task loader and add all the tasks we need to load
+        mLoader.start(context);
+        mLoadQueue.addTasks(tasksToLoad);
 
-        // From back to front, add each task to the task stack
+        // Update the package monitor with the list of packages to listen for
+        mPackageMonitor.setTasks(taskKeys);
+
+        return root;
+    }
+
+    /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */
+    public TaskStack getTaskStack(SystemServicesProxy ssp, Resources res,
+            int preloadTaskId, int preloadTaskCount,
+            boolean loadTaskThumbnails, List<Task.TaskKey> taskKeysOut,
+            List<Task> tasksToLoadOut) {
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
+        HashMap<ComponentName, ActivityInfoHandle> activityInfoCache =
+                new HashMap<ComponentName, ActivityInfoHandle>();
+        ArrayList<Task> tasksToAdd = new ArrayList<Task>();
+        TaskStack stack = new TaskStack();
+
         int taskCount = tasks.size();
         for (int i = 0; i < taskCount; i++) {
             ActivityManager.RecentTaskInfo t = tasks.get(i);
+
+            // Get an existing activity info handle if possible
+            ComponentName cn = t.baseIntent.getComponent();
+            ActivityInfoHandle infoHandle = new ActivityInfoHandle();
+            boolean hasCachedActivityInfo = false;
+            if (activityInfoCache.containsKey(cn)) {
+                infoHandle = activityInfoCache.get(cn);
+                hasCachedActivityInfo = true;
+            }
+
+            // Compose the task key
             Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
                     t.firstActiveTime, t.lastActiveTime);
-            ComponentName cn = t.baseIntent.getComponent();
-            ActivityInfo info = null;
 
-            ActivityManager.TaskDescription av = t.taskDescription;
-            String activityLabel  = null;
-            Drawable activityIcon = mDefaultApplicationIcon;
-            int activityColor = config.taskBarViewDefaultBackgroundColor;
-            boolean loadedActivityIcon = false;
-            if (av != null) {
-                activityLabel = av.getLabel();
-                activityIcon = mTaskDescriptionIconCache.getAndInvalidateIfModified(taskKey);
-                if (activityIcon == null) {
-                    activityIcon = (av.getIcon() != null) ?
-                        ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null;
-                    if (activityIcon != null) {
-                        mTaskDescriptionIconCache.put(taskKey, activityIcon);
-                    }
-                }
-                if (av.getPrimaryColor() != 0) {
-                    activityColor = av.getPrimaryColor();
-                }
-                loadedActivityIcon = (activityIcon != null);
-            }
-            // If there is no activity label, then try and read it from the label cache before
-            // loading it from the system
-            if (activityLabel == null) {
-                activityLabel = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
-                if (activityLabel == null) {
-                    if (info == null) {
-                        info = ssp.getActivityInfo(cn, t.userId);
-                    }
-                    activityLabel = ssp.getActivityLabel(info);
-                    mActivityLabelCache.put(taskKey, activityLabel);
-                }
+            // Determine whether to preload this task
+            boolean preloadTask = false;
+            if (preloadTaskId > 0) {
+                preloadTask = (t.id == preloadTaskId);
+            } else if (preloadTaskCount > 0) {
+                preloadTask = (i >= (taskCount - preloadTaskCount));
             }
 
-            // Create a new task
+            // Load the label, icon, and color
+            String activityLabel  = getAndUpdateActivityLabel(taskKey, t.taskDescription,
+                    ssp, infoHandle);
+            Drawable activityIcon = getAndUpdateActivityIcon(taskKey, t.taskDescription,
+                    ssp, res, infoHandle, preloadTask);
+            int activityColor = getActivityPrimaryColor(t.taskDescription, config);
+
+            // Update the activity info cache
+            if (!hasCachedActivityInfo && infoHandle.info != null) {
+                activityInfoCache.put(cn, infoHandle);
+            }
+
+            // Add the task to the stack
             Task task = new Task(taskKey, (t.id > -1), t.affiliatedTaskId, t.affiliatedTaskColor,
                     activityLabel, activityIcon, activityColor, (i == (taskCount - 1)),
                     config.lockToAppEnabled);
 
-            // Preload the specified number of apps
-            if (i >= (taskCount - preloadCount)) {
-                // Load the icon from the cache if possible (only if we don't have an activity icon)
-                if (!loadedActivityIcon) {
-                    task.applicationIcon =
-                            mApplicationIconCache.getAndInvalidateIfModified(taskKey);
-                    if (task.applicationIcon == null) {
-                        // Load the icon from the system
-                        if (info == null) {
-                            info = ssp.getActivityInfo(cn, t.userId);
-                        }
-                        task.applicationIcon = ssp.getActivityIcon(info, taskKey.userId);
-                        if (task.applicationIcon != null) {
-                            mApplicationIconCache.put(taskKey, task.applicationIcon);
-                        }
-                    }
-                    if (task.applicationIcon == null) {
-                        // Either the task has changed since the last active time, or it was not
-                        // previously cached, so try and load the task anew.
-                        tasksToLoad.add(task);
-                    }
-                }
-
+            if (preloadTask && loadTaskThumbnails) {
                 // Load the thumbnail from the cache if possible
                 task.thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
                 if (task.thumbnail == null) {
@@ -408,54 +459,20 @@
                         mThumbnailCache.put(taskKey, task.thumbnail);
                     }
                 }
-                if (task.thumbnail == null) {
+                if (task.thumbnail == null && tasksToLoadOut != null) {
                     // Either the task has changed since the last active time, or it was not
                     // previously cached, so try and load the task anew.
-                    tasksToLoad.add(task);
+                    tasksToLoadOut.add(task);
                 }
             }
 
+            // Add to the list of task keys
+            if (taskKeysOut != null) {
+                taskKeysOut.add(taskKey);
+            }
             // Add the task to the stack
             tasksToAdd.add(task);
         }
-
-        // Simulate the groupings that we describe
-        stack.setTasks(tasksToAdd);
-        stack.createAffiliatedGroupings(config);
-
-        // Start the task loader and add all the tasks we need to load
-        mLoader.start(context);
-        mLoadQueue.addTasks(tasksToLoad);
-
-        // Update the package monitor with the list of packages to listen for
-        mPackageMonitor.setTasks(tasks);
-
-        return root;
-    }
-
-    /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */
-    public static TaskStack getShallowTaskStack(SystemServicesProxy ssp, int numTasks,
-            Resources resources) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp, numTasks);
-        ArrayList<Task> tasksToAdd = new ArrayList<Task>();
-        TaskStack stack = new TaskStack();
-
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            ActivityManager.RecentTaskInfo t = tasks.get(i);
-            ActivityManager.TaskDescription av = t.taskDescription;
-
-            BitmapDrawable icon = null;
-            if (av.getIcon() != null) {
-                icon = new BitmapDrawable(resources, av.getIcon());
-            }
-            Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
-                    t.firstActiveTime, t.lastActiveTime);
-            tasksToAdd.add(new Task(taskKey, true, t.affiliatedTaskId, t.affiliatedTaskColor,
-                    av.getLabel(), icon, av.getPrimaryColor(), (i == (taskCount - 1)),
-                    config.lockToAppEnabled));
-        }
         stack.setTasks(tasksToAdd);
         stack.createAffiliatedGroupings(config);
         return stack;
@@ -525,21 +542,18 @@
                 // We are leaving recents, so trim the data a bit
                 mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
                 mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
-                mTaskDescriptionIconCache.trimToSize(mMaxIconCacheSize / 2);
                 break;
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
             case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                 // We are going to be low on memory
                 mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
                 mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
-                mTaskDescriptionIconCache.trimToSize(mMaxIconCacheSize / 4);
                 break;
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
             case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                 // We are low on memory, so release everything
                 mThumbnailCache.evictAll();
                 mApplicationIconCache.evictAll();
-                mTaskDescriptionIconCache.evictAll();
                 // The cache is small, only clear the label cache when we are critical
                 mActivityLabelCache.evictAll();
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 34f73c6..d6889d0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -33,6 +33,8 @@
     Rect mClipRect = new Rect();
     Rect mOutlineClipRect = new Rect();
     int mCornerRadius;
+    float mAlpha = 1f;
+    final float mMinAlpha = 0.25f;
 
     ObjectAnimator mClipTopAnimator;
     ObjectAnimator mClipRightAnimator;
@@ -51,6 +53,7 @@
 
     @Override
     public void getOutline(View view, Outline outline) {
+        outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
         outline.setRoundRect(Math.max(mClipRect.left, mOutlineClipRect.left),
                 Math.max(mClipRect.top, mOutlineClipRect.top),
                 mSourceView.getMeasuredWidth() - Math.max(mClipRect.right, mOutlineClipRect.right),
@@ -58,6 +61,14 @@
                 mCornerRadius);
     }
 
+    /** Sets the view outline alpha. */
+    void setAlpha(float alpha) {
+        if (Float.compare(alpha, mAlpha) != 0) {
+            mAlpha = alpha;
+            mSourceView.invalidateOutline();
+        }
+    }
+
     /** Animates the top clip. */
     void animateClipTop(int top, int duration, ValueAnimator.AnimatorUpdateListener updateListener) {
         if (mClipTopAnimator != null) {
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 47fda5b..1ac3bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -477,10 +477,10 @@
             if (!task.group.isFrontMostTask(task)) {
                 // For affiliated tasks that are behind other tasks, we must animate the front cards
                 // out of view before starting the task transition
-                stackView.startLaunchTaskAnimation(tv, launchRunnable);
+                stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask);
             } else {
                 // Otherwise, we can start the task transition immediately
-                stackView.startLaunchTaskAnimation(tv, null);
+                stackView.startLaunchTaskAnimation(tv, null, lockToTask);
                 postDelayed(launchRunnable, 17);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index e0298ab..fa44551 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -28,6 +28,7 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.animation.LinearInterpolator;
+import com.android.systemui.recents.RecentsConfiguration;
 
 /**
  * This class facilitates swipe to dismiss. It defines an interface to be implemented by the
@@ -50,7 +51,7 @@
     private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
     private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
     private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
-    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
+    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
 
     public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
                                                  // where fade starts
@@ -265,6 +266,7 @@
         ValueAnimator anim = createTranslationAnimation(view, 0);
         int duration = SNAP_ANIM_LEN;
         anim.setDuration(duration);
+        anim.setInterpolator(RecentsConfiguration.getInstance().linearOutSlowInInterpolator);
         anim.addUpdateListener(new AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
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 861011f..dbed136 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -105,7 +105,7 @@
         mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
         mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm);
         mStackScroller.setCallbacks(this);
-        mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
+        mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller);
         mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
             @Override
             public void run() {
@@ -647,17 +647,17 @@
     }
 
     /** Animates a task view in this stack as it launches. */
-    public void startLaunchTaskAnimation(TaskView tv, final Runnable r) {
+    public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
         Task launchTargetTask = tv.getTask();
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             TaskView t = (TaskView) getChildAt(i);
             if (t == tv) {
-                t.startLaunchTaskAnimation(r, true, true);
+                t.startLaunchTaskAnimation(r, true, true, lockToTask);
             } else {
                 boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(),
                         launchTargetTask);
-                t.startLaunchTaskAnimation(null, false, occludesLaunchTarget);
+                t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask);
             }
         }
     }
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 2c0dc44..5852b88 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -133,12 +133,8 @@
         stopBoundScrollAnimation();
 
         mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll);
-        mScrollAnimator.setDuration(200);
-        // We would have to project the difference into the screen coords, and then use that as the
-        // duration
-//        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
-//                curScroll, 250));
-        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+        mScrollAnimator.setDuration(mConfig.taskStackScrollDuration);
+        mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator);
         mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator 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 374a27f..8f9b4c2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -23,11 +23,13 @@
 import android.view.ViewConfiguration;
 import android.view.ViewParent;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
 
 /* Handles touch events for a TaskStackView. */
 class TaskStackViewTouchHandler implements SwipeHelper.Callback {
     static int INACTIVE_POINTER_ID = -1;
 
+    RecentsConfiguration mConfig;
     TaskStackView mSv;
     TaskStackViewScroller mScroller;
     VelocityTracker mVelocityTracker;
@@ -52,7 +54,8 @@
     SwipeHelper mSwipeHelper;
     boolean mInterceptedBySwipeHelper;
 
-    public TaskStackViewTouchHandler(Context context, TaskStackView sv, TaskStackViewScroller scroller) {
+    public TaskStackViewTouchHandler(Context context, TaskStackView sv,
+            RecentsConfiguration config, TaskStackViewScroller scroller) {
         ViewConfiguration configuration = ViewConfiguration.get(context);
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
@@ -60,6 +63,7 @@
         mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
         mSv = sv;
         mScroller = scroller;
+        mConfig = config;
 
         float densityScale = context.getResources().getDisplayMetrics().density;
         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
@@ -258,7 +262,7 @@
                     if (Float.compare(overScrollAmount, 0f) != 0) {
                         // Bound the overscroll to a fixed amount, and inversely scale the y-movement
                         // relative to how close we are to the max overscroll
-                        float maxOverScroll = 0.25f;
+                        float maxOverScroll = mConfig.taskStackOverscrollPct;
                         deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
                                 / maxOverScroll));
                     }
@@ -280,7 +284,6 @@
                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
                 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
-                    // XXX: Should this be calculated as a percentage of a curve?
                     int overscrollRange = (int) (Math.min(1f,
                             Math.abs((float) velocity / mMaximumVelocity)) *
                             Constants.Values.TaskStackView.TaskStackOverscrollRange);
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 dfbcce1..eecc170 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -471,7 +471,7 @@
 
     /** Animates this task view as it exits recents */
     void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
-            boolean occludesLaunchTarget) {
+            boolean occludesLaunchTarget, boolean lockToTask) {
         if (isLaunchingTask) {
             // Disable the thumbnail clip
             mThumbnailView.disableTaskBarClip();
@@ -487,11 +487,14 @@
             }
 
             // Animate the action button away
-            float toScale = 0.9f;
+            if (!lockToTask) {
+                float toScale = 0.9f;
+                mActionButtonView.animate()
+                        .scaleX(toScale)
+                        .scaleY(toScale);
+            }
             mActionButtonView.animate()
                     .alpha(0f)
-                    .scaleX(toScale)
-                    .scaleY(toScale)
                     .setStartDelay(0)
                     .setDuration(mConfig.taskBarExitAnimDuration)
                     .setInterpolator(mConfig.fastOutLinearInInterpolator)
@@ -621,6 +624,7 @@
     /** Sets the current task progress. */
     public void setTaskProgress(float p) {
         mTaskProgress = p;
+        mViewBounds.setAlpha(p);
         updateDimFromTaskProgress();
     }