Merge "android.widget.Toolbar"
diff --git a/docs/html/tools/device.jd b/docs/html/tools/device.jd
index a1fb817..ccd5903 100644
--- a/docs/html/tools/device.jd
+++ b/docs/html/tools/device.jd
@@ -30,9 +30,8 @@
 you don't yet have a device, check with the service providers in your area to determine which
 Android-powered devices are available.</p>
 
-<p>If you want a SIM-unlocked phone, then you might consider a Nexus phone. To find a place
-to purchase the Nexus S and other Android-powered devices, visit <a
-href="http://www.google.com/phone/detail/nexus-s">google.com/phone</a>.</p>
+<p>If you want a SIM-unlocked phone, then you might consider a Nexus phone. To purchase a
+Nexus phone, visit the <a href="https://play.google.com/store/devices">Google Play</a> store.</p>
 
 <p class="note"><strong>Note:</strong> When developing on a device, keep in mind that you should
 still use the <a
@@ -90,11 +89,11 @@
             <p>Use this format to add each vendor to the file:<br/>
               <code>SUBSYSTEM==&quot;usb&quot;, ATTR{idVendor}==&quot;0bb4&quot;, MODE=&quot;0666&quot;, GROUP=&quot;plugdev&quot;</code>
               <br /><br />
-              
+
               In this example, the vendor ID is for HTC. The <code>MODE</code>
 assignment specifies read/write permissions, and <code>GROUP</code> defines
 which Unix group  owns the device node. </p>
-            
+
             <p class="note"><strong>Note:</strong> The rule syntax
 may vary slightly depending on your  environment. Consult the <code>udev</code>
 documentation for your system as needed. For an overview of rule syntax, see
@@ -138,7 +137,7 @@
 
 <p>This table provides a reference to the vendor IDs needed in order to add USB
 device support on Linux. The USB Vendor ID is the value given to the
-<code>ATTR{idVendor}</code> property in the rules file, as described 
+<code>ATTR{idVendor}</code> property in the rules file, as described
 above.</p>
 
 <table>
diff --git a/docs/html/training/basics/actionbar/styling.jd b/docs/html/training/basics/actionbar/styling.jd
index 1f76e03..4128a97 100644
--- a/docs/html/training/basics/actionbar/styling.jd
+++ b/docs/html/training/basics/actionbar/styling.jd
@@ -144,13 +144,13 @@
 &lt;resources>
     &lt;!-- the theme applied to the application or activity -->
     &lt;style name="CustomActionBarTheme"
-           parent="&#64;style/Theme.Holo.Light.DarkActionBar">
+           parent="&#64;android:style/Theme.Holo.Light.DarkActionBar">
         &lt;item name="android:actionBarStyle">&#64;style/MyActionBar&lt;/item>
     &lt;/style>
 
     &lt;!-- ActionBar styles -->
     &lt;style name="MyActionBar"
-           parent="&#64;style/Widget.Holo.Light.ActionBar.Solid.Inverse">
+           parent="&#64;android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
         &lt;item name="android:background">&#64;drawable/actionbar_background&lt;/item>
     &lt;/style>
 &lt;/resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 77ab17b..77944c8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -107,5 +107,10 @@
 
     <!-- milliseconds before the heads up notification accepts touches. -->
     <integer name="heads_up_sensitivity_delay">700</integer>
+
+    <!-- The min animation duration for animating views that are currently visible. -->
+    <integer name="recents_filter_animate_current_views_min_duration">175</integer>
+    <!-- The min animation duration for animating views that are newly visible. -->
+    <integer name="recents_filter_animate_new_views_min_duration">125</integer>
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1c6d5ad..2c8f9a1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -236,6 +236,10 @@
     <!-- The size of the activity icon in the recents task view. -->
     <dimen name="recents_task_view_activity_icon_size">60dp</dimen>
 
+    <!-- Used to calculate the translation animation duration, the expected amount of movement 
+         in dps over one second of time. -->
+    <dimen name="recents_animation_movement_in_dps_per_second">800dp</dimen>
+
     <!-- Space below the notification stack -->
     <dimen name="notification_stack_margin_bottom">0dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/BakedBezierInterpolator.java b/packages/SystemUI/src/com/android/systemui/recents/BakedBezierInterpolator.java
new file mode 100644
index 0000000..b085211
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/BakedBezierInterpolator.java
@@ -0,0 +1,67 @@
+package com.android.systemui.recents;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * A pre-baked bezier-curved interpolator for quantum-paper transitions.
+ */
+public class BakedBezierInterpolator implements TimeInterpolator {
+    public static final BakedBezierInterpolator INSTANCE = new BakedBezierInterpolator();
+
+    /**
+     * Use the INSTANCE variable instead of instantiating.
+     */
+    private BakedBezierInterpolator() {
+        super();
+    }
+
+    /**
+     * Lookup table values.
+     * Generated using a Bezier curve from (0,0) to (1,1) with control points:
+     * P0 (0,0)
+     * P1 (0.4, 0)
+     * P2 (0.2, 1.0)
+     * P3 (1.0, 1.0)
+     *
+     * Values sampled with x at regular intervals between 0 and 1.
+     *
+     * These values were generated using:
+     *   ./scripts/bezier_interpolator_values_gen.py 0.4 0.2
+     */
+    private static final float[] VALUES = new float[] {
+        0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f,
+        0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f,
+        0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f,
+        0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f,
+        0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f,
+        0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f,
+        0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f,
+        0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f,
+        0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f,
+        0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f
+    };
+
+    private static final float STEP_SIZE = 1.0f / (VALUES.length - 1);
+
+    @Override
+    public float getInterpolation(float input) {
+        if (input >= 1.0f) {
+            return 1.0f;
+        }
+
+        if (input <= 0f) {
+            return 0f;
+        }
+
+        int position = Math.min(
+                (int)(input * (VALUES.length - 1)),
+                VALUES.length - 2);
+
+        float quantized = position * STEP_SIZE;
+        float difference = input - quantized;
+        float weight = difference / STEP_SIZE;
+
+        return VALUES[position] + weight * (VALUES[position + 1] - VALUES[position]);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 8c5c8fa..86f188e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -34,7 +34,7 @@
             // For debugging, this enables us to create mock recents tasks
             public static final boolean EnableSystemServicesProxy = false;
             // For debugging, this defines the number of mock recents packages to create
-            public static final int SystemServicesProxyMockPackageCount = 12;
+            public static final int SystemServicesProxyMockPackageCount = 3;
             // For debugging, this defines the number of mock recents tasks to create
             public static final int SystemServicesProxyMockTaskCount = 75;
 
@@ -82,16 +82,8 @@
         }
 
         public static class TaskStackView {
-            public static class Animation {
-                public static final int TaskRemovedReshuffleDuration = 200;
-                public static final int SnapScrollBackDuration = 650;
-                public static final int FilteredCurrentViewsDuration = 150;
-                public static final int FilteredNewViewsDuration = 200;
-                public static final int UnfilteredCurrentViewsDuration = 150;
-                public static final int UnfilteredNewViewsDuration = 200;
-            }
-
             public static final int TaskStackOverscrollRange = 150;
+            public static final int FilterStartDelay = 25;
 
             // The padding will be applied to the smallest dimension, and then applied to all sides
             public static final float StackPaddingPct = 0.15f;
@@ -106,12 +98,6 @@
         }
 
         public static class TaskView {
-            public static class Animation {
-                public static final int TaskDataUpdatedFadeDuration = 250;
-                public static final int TaskIconOnEnterDuration = 175;
-                public static final int TaskIconOnLeavingDuration = 75;
-            }
-
             public static final boolean AnimateFrontTaskIconOnEnterRecents = true;
             public static final boolean AnimateFrontTaskIconOnLeavingRecents = true;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 3d47cb6..4a0de0b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -17,11 +17,11 @@
 package com.android.systemui.recents;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
+import com.android.systemui.R;
 
 
 /** A static Recents configuration for the current context
@@ -34,6 +34,11 @@
     public Rect systemInsets = new Rect();
     public Rect displayRect = new Rect();
 
+    public float animationPxMovementPerSecond;
+
+    public int filteringCurrentViewsMinAnimDuration;
+    public int filteringNewViewsMinAnimDuration;
+
     /** Private constructor */
     private RecentsConfiguration() {}
 
@@ -58,6 +63,12 @@
         mDisplayMetrics = dm;
 
         displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
+        animationPxMovementPerSecond =
+                res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
+        filteringCurrentViewsMinAnimDuration =
+                res.getInteger(R.integer.recents_filter_animate_current_views_min_duration);
+        filteringNewViewsMinAnimDuration =
+                res.getInteger(R.integer.recents_filter_animate_new_views_min_duration);
     }
 
     public void updateSystemInsets(Rect insets) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
index f147fbc6..efcd948 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
@@ -51,9 +51,9 @@
 
         if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
             // Create a dummy icon
-            mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+            mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
             Canvas c = new Canvas(mDummyIcon);
-            c.drawColor(0xFFFF0000);
+            c.drawColor(0xFF999999);
             c.setBitmap(null);
         }
     }
@@ -77,7 +77,7 @@
                 rti.id = rti.persistentId = i;
                 rti.baseIntent = new Intent();
                 rti.baseIntent.setComponent(cn);
-                rti.description = rti.activityLabel =
+                rti.description = rti.activityLabel = "" + i + " - " +
                         Long.toString(Math.abs(new Random().nextLong()), 36);
                 if (i % 2 == 0) {
                     rti.activityIcon = Bitmap.createBitmap(mDummyIcon);
@@ -118,7 +118,7 @@
         if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
             Bitmap thumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
             Canvas c = new Canvas(thumbnail);
-            c.drawColor(0xFF00ff00);
+            c.drawColor(0xff333333);
             c.setBitmap(null);
             return thumbnail;
         }
@@ -178,7 +178,7 @@
 
         // If we are mocking, then return a mock label
         if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
-            return new ColorDrawable(0xFFff0000);
+            return new ColorDrawable(0xFF666666);
         }
 
         return info.loadIcon(mPm);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
index 9048cba..4a1b3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
@@ -20,6 +20,19 @@
 
 /* Common code */
 public class Utilities {
+    /**
+     * Calculates a consistent animation duration (ms) for all animations depending on the movement
+     * of the object being animated.
+     */
+    public static int calculateTranslationAnimationDuration(int distancePx) {
+        return calculateTranslationAnimationDuration(distancePx, 100);
+    }
+    public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) {
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        return Math.max(minDuration, (int) (1000f /* ms/s */ *
+                (Math.abs(distancePx) / config.animationPxMovementPerSecond)));
+    }
+
     /** Scales a rect about its centroid */
     public static void scaleRectAboutCenter(Rect r, float scale) {
         if (scale != 1.0f) {
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 dfd608c..fa06764 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -35,11 +35,11 @@
 import android.widget.FrameLayout;
 import android.widget.OverScroller;
 import com.android.systemui.R;
+import com.android.systemui.recents.BakedBezierInterpolator;
 import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.RecentsTaskLoader;
-import com.android.systemui.recents.SystemServicesProxy;
 import com.android.systemui.recents.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
@@ -76,6 +76,9 @@
     OverScroller mScroller;
     ObjectAnimator mScrollAnimator;
 
+    // Filtering
+    AnimatorSet mFilterChildrenAnimator;
+
     // Optimizations
     int mHwLayersRefCount;
     int mStackViewsAnimationDuration;
@@ -180,7 +183,8 @@
      */
     private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
                                                             int stackScroll,
-                                                            int[] visibleRangeOut) {
+                                                            int[] visibleRangeOut,
+                                                            boolean boundTranslationsToRect) {
         // XXX: Optimization: Use binary search to find the visible range
 
         ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
@@ -196,6 +200,10 @@
                 }
                 lastVisibleIndex = i;
             }
+
+            if (boundTranslationsToRect) {
+                transform.translationY = Math.min(transform.translationY, mRect.bottom);
+            }
         }
         if (visibleRangeOut != null) {
             visibleRangeOut[0] = firstVisibleIndex;
@@ -219,7 +227,7 @@
             int stackScroll = getStackScroll();
             ArrayList<Task> tasks = mStack.getTasks();
             ArrayList<TaskViewTransform> taskTransforms = getStackTransforms(tasks, stackScroll,
-                    visibleRange);
+                    visibleRange, false);
 
             // Update the visible state of all the tasks
             int taskCount = tasks.size();
@@ -286,7 +294,7 @@
     }
 
     /** Animates the stack scroll into bounds */
-    ObjectAnimator animateBoundScroll(int duration) {
+    ObjectAnimator animateBoundScroll() {
         int curScroll = getStackScroll();
         int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
         if (newScroll != curScroll) {
@@ -298,16 +306,18 @@
             abortBoundScrollAnimation();
 
             // Start a new scroll animation
-            animateScroll(curScroll, newScroll, duration);
+            animateScroll(curScroll, newScroll);
             mScrollAnimator.start();
         }
         return mScrollAnimator;
     }
 
     /** Animates the stack scroll */
-    void animateScroll(int curScroll, int newScroll, int duration) {
+    void animateScroll(int curScroll, int newScroll) {
         mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
-        mScrollAnimator.setDuration(duration);
+        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
+                curScroll, 250));
+        mScrollAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
         mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
@@ -625,7 +635,8 @@
         }
 
         updateMinMaxScroll(true);
-        requestSynchronizeStackViewsWithModel(Constants.Values.TaskStackView.Animation.TaskRemovedReshuffleDuration);
+        int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height());
+        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
     }
 
     @Override
@@ -635,20 +646,22 @@
         // (filtered) stack
         // XXX: Use HW Layers
 
-        // Stash the scroll for us to restore to when we unfilter
+        // Stash the scroll and filtered task for us to restore to when we unfilter
         mStashedScroll = getStackScroll();
 
         // Compute the transforms of the items in the current stack
         final ArrayList<TaskViewTransform> curTaskTransforms =
-                getStackTransforms(curStack, mStashedScroll, null);
+                getStackTransforms(curStack, mStashedScroll, null, true);
 
-        // Bound the new stack scroll
+        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
         updateMinMaxScroll(false);
+        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
+        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
         boundScrollRaw();
 
-        // Compute the transforms of the items in the new stack
+        // Compute the transforms of the items in the new stack after setting the new scroll
         final ArrayList<TaskViewTransform> taskTransforms =
-                getStackTransforms(mStack.getTasks(), getStackScroll(), null);
+                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
 
         // Animate all of the existing views on screen either out of view (if they are not visible
         // in the new stack) or to their final positions in the new stack
@@ -656,13 +669,16 @@
         final ArrayList<Task> tasks = mStack.getTasks();
         ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
         int childCount = getChildCount();
+        int movement = 0;
         for (int i = 0; i < childCount; i++) {
             TaskView tv = (TaskView) getChildAt(i);
             Task task = tv.getTask();
             TaskViewTransform toTransform;
             int taskIndex = tasks.indexOf(task);
-            if ((taskIndex < 0) || !taskTransforms.get(taskIndex).visible) {
-                // Compose a new transform that animates the task view out of view
+
+            boolean willBeInvisible = (taskIndex < 0) || !taskTransforms.get(taskIndex).visible;
+            if (willBeInvisible) {
+                // Compose a new transform that fades and slides the task out of view
                 TaskViewTransform fromTransform = curTaskTransforms.get(curStack.indexOf(task));
                 toTransform = new TaskViewTransform(fromTransform);
                 tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
@@ -671,23 +687,49 @@
                 childrenToReturnToPool.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()));
             }
             childViewAnims.add(tv.getAnimatorToTaskTransform(toTransform));
         }
 
-        AnimatorSet childViewAnimSet = new AnimatorSet();
-        childViewAnimSet.setDuration(
-                Constants.Values.TaskStackView.Animation.FilteredCurrentViewsDuration);
-        childViewAnimSet.addListener(new AnimatorListenerAdapter() {
+        // Cancel the previous animation
+        if (mFilterChildrenAnimator != null) {
+            mFilterChildrenAnimator.cancel();
+            mFilterChildrenAnimator.removeAllListeners();
+        }
+
+        // Create a new animation for the existing children
+        final RecentsConfiguration config = RecentsConfiguration.getInstance();
+        mFilterChildrenAnimator = new AnimatorSet();
+        mFilterChildrenAnimator.setDuration(
+                Utilities.calculateTranslationAnimationDuration(movement,
+                        config.filteringCurrentViewsMinAnimDuration));
+        mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
+        mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() {
+            boolean isCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                isCancelled = true;
+            }
+
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (isCancelled) return;
+
                 // Return all the removed children to the view pool
                 for (TaskView tv : childrenToReturnToPool) {
                     mViewPool.returnViewToPool(tv);
                 }
 
                 // For views that are not already visible, animate them in
+                ArrayList<Animator> newViewsAnims = new ArrayList<Animator>();
                 int taskCount = tasks.size();
+                int movement = 0;
+                int offset = 0;
                 for (int i = 0; i < taskCount; i++) {
                     Task task = tasks.get(i);
                     TaskViewTransform toTransform = taskTransforms.get(i);
@@ -697,20 +739,38 @@
                         TaskView tv = getChildViewForTask(task);
                         if (tv == null) {
                             tv = mViewPool.pickUpViewFromPool(task, task);
+                            // Compose a new transform that fades and slides the new task in
+                            fromTransform = new TaskViewTransform(toTransform);
+                            tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
+                            tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
 
-                            // Animate from the current position to the new position
-                            tv.prepareTaskTransformForFilterTaskVisible(fromTransform);
-                            tv.updateViewPropertiesToTaskTransform(fromTransform,
-                                    toTransform,
-                                    Constants.Values.TaskStackView.Animation.FilteredNewViewsDuration);
+                            Animator anim = tv.getAnimatorToTaskTransform(toTransform);
+                            anim.setStartDelay(offset *
+                                    Constants.Values.TaskStackView.FilterStartDelay);
+                            newViewsAnims.add(anim);
+
+                            // Use the movement of the newly visible views to calculate the duration
+                            // of the animation
+                            movement = Math.max(movement, Math.abs(toTransform.translationY -
+                                    fromTransform.translationY));
+                            offset++;
                         }
                     }
+
+                    // Animate the new views in
+                    mFilterChildrenAnimator = new AnimatorSet();
+                    mFilterChildrenAnimator.setDuration(
+                            Utilities.calculateTranslationAnimationDuration(movement,
+                                    config.filteringNewViewsMinAnimDuration));
+                    mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
+                    mFilterChildrenAnimator.playTogether(newViewsAnims);
+                    mFilterChildrenAnimator.start();
                 }
                 invalidate();
             }
         });
-        childViewAnimSet.playTogether(childViewAnims);
-        childViewAnimSet.start();
+        mFilterChildrenAnimator.playTogether(childViewAnims);
+        mFilterChildrenAnimator.start();
     }
 
     @Override
@@ -718,16 +778,16 @@
         // Compute the transforms of the items in the current stack
         final int curScroll = getStackScroll();
         final ArrayList<TaskViewTransform> curTaskTransforms =
-                getStackTransforms(curStack, curScroll, null);
+                getStackTransforms(curStack, curScroll, null, true);
 
         // Restore the stashed scroll
         updateMinMaxScroll(false);
         setStackScrollRaw(mStashedScroll);
         boundScrollRaw();
 
-        // Compute the transforms of the items in the new stack
+        // Compute the transforms of the items in the new stack after restoring the stashed scroll
         final ArrayList<TaskViewTransform> taskTransforms =
-                getStackTransforms(mStack.getTasks(), getStackScroll(), null);
+                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
 
         // 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
@@ -735,29 +795,55 @@
         final ArrayList<Task> tasks = mStack.getTasks();
         ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
         int childCount = getChildCount();
+        int movement = 0;
         for (int i = 0; i < childCount; i++) {
             TaskView tv = (TaskView) getChildAt(i);
             Task task = tv.getTask();
             int taskIndex = tasks.indexOf(task);
-            TaskViewTransform transform;
+            TaskViewTransform toTransform;
 
             // If the view is no longer visible, then we should just animate it out
-            if (taskIndex < 0 || !taskTransforms.get(taskIndex).visible) {
-                transform = new TaskViewTransform(curTaskTransforms.get(curStack.indexOf(task)));
-                tv.prepareTaskTransformForFilterTaskVisible(transform);
+            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
+            if (willBeInvisible) {
+                toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
+                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
                 childrenToRemove.add(tv);
             } else {
-                transform = taskTransforms.get(taskIndex);
+                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()));
             }
-            childViewAnims.add(tv.getAnimatorToTaskTransform(transform));
+
+            Animator anim = tv.getAnimatorToTaskTransform(toTransform);
+            childViewAnims.add(anim);
         }
 
-        AnimatorSet childViewAnimSet = new AnimatorSet();
-        childViewAnimSet.setDuration(
-                Constants.Values.TaskStackView.Animation.UnfilteredCurrentViewsDuration);
-        childViewAnimSet.addListener(new AnimatorListenerAdapter() {
+        // Cancel the previous animation
+        if (mFilterChildrenAnimator != null) {
+            mFilterChildrenAnimator.cancel();
+            mFilterChildrenAnimator.removeAllListeners();
+        }
+
+        // Create a new animation for the existing children
+        final RecentsConfiguration config = RecentsConfiguration.getInstance();
+        mFilterChildrenAnimator = new AnimatorSet();
+        mFilterChildrenAnimator.setDuration(
+                Utilities.calculateTranslationAnimationDuration(movement,
+                        config.filteringCurrentViewsMinAnimDuration));
+        mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
+        mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() {
+            boolean isCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                isCancelled = true;
+            }
+
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (isCancelled) return;
+
                 // Return all the removed children to the view pool
                 for (TaskView tv : childrenToRemove) {
                     mViewPool.returnViewToPool(tv);
@@ -768,8 +854,8 @@
 
                 // For views that are not already visible, animate them in
                 ArrayList<Animator> newViewAnims = new ArrayList<Animator>();
-                AnimatorSet newViewAnimSet = new AnimatorSet();
                 int taskCount = tasks.size();
+                int movement = 0;
                 int offset = 0;
                 for (int i = 0; i < taskCount; i++) {
                     Task task = tasks.get(i);
@@ -780,34 +866,46 @@
                             // For views that are not already visible, animate them in
                             tv = mViewPool.pickUpViewFromPool(task, task);
 
-                            // Animate in this new view
+                            // Compose a new transform to fade and slide the new task in
                             TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
                             tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
                             tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
-                            newViewAnims.add(tv.getAnimatorToTaskTransform(toTransform));
+
+                            Animator anim = tv.getAnimatorToTaskTransform(toTransform);
+                            anim.setStartDelay(offset *
+                                    Constants.Values.TaskStackView.FilterStartDelay);
+                            newViewAnims.add(anim);
+                            // Use the movement of the newly visible views to calculate the duration
+                            // of the animation
+                            movement = Math.max(movement,
+                                    Math.abs(toTransform.translationY - fromTransform.translationY));
                             offset++;
                         }
                     }
                 }
 
                 // Run the animation
-                newViewAnimSet.setDuration(
-                        Constants.Values.TaskStackView.Animation.UnfilteredNewViewsDuration);
-                newViewAnimSet.playTogether(newViewAnims);
-                newViewAnimSet.addListener(new AnimatorListenerAdapter() {
+                mFilterChildrenAnimator = new AnimatorSet();
+                mFilterChildrenAnimator.setDuration(
+                        Utilities.calculateTranslationAnimationDuration(movement,
+                                config.filteringNewViewsMinAnimDuration));
+                mFilterChildrenAnimator.playTogether(newViewAnims);
+                mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         // Decrement the hw layers ref count
                         decHwLayersRefCount("unfilteredNewViews");
                     }
                 });
-                newViewAnimSet.start();
-
+                mFilterChildrenAnimator.start();
                 invalidate();
             }
         });
-        childViewAnimSet.playTogether(childViewAnims);
-        childViewAnimSet.start();
+        mFilterChildrenAnimator.playTogether(childViewAnims);
+        mFilterChildrenAnimator.start();
+
+        // Clear the saved vars
+        mStashedScroll = 0;
     }
 
     /**** ViewPoolConsumer Implementation ****/
@@ -1056,7 +1154,7 @@
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP: {
                 // Animate the scroll back if we've cancelled
-                mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
+                mSv.animateBoundScroll();
                 // Disable HW layers
                 if (mIsScrolling) {
                     mSv.decHwLayersRefCount("stackScroll");
@@ -1186,7 +1284,7 @@
                 } else if (mSv.isScrollOutOfBounds()) {
                     // Animate the scroll back into bounds
                     // XXX: Make this animation a function of the velocity OR distance
-                    mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
+                    mSv.animateBoundScroll();
                 }
 
                 if (mIsScrolling) {
@@ -1220,7 +1318,7 @@
                 if (mSv.isScrollOutOfBounds()) {
                     // Animate the scroll back into bounds
                     // XXX: Make this animation a function of the velocity OR distance
-                    mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
+                    mSv.animateBoundScroll();
                 }
                 mActivePointerId = INACTIVE_POINTER_ID;
                 mIsScrolling = false;
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 2c27d44..e99fecb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -20,20 +20,24 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.BakedBezierInterpolator;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.Utilities;
 import com.android.systemui.recents.model.Task;
 
+import java.util.Random;
+
 
 /* A task view */
 public class TaskView extends FrameLayout implements View.OnClickListener, Task.TaskCallbacks {
@@ -131,7 +135,7 @@
                     .scaleY(toTransform.scale)
                     .alpha(toTransform.alpha)
                     .setDuration(duration)
-                    .setInterpolator(new AccelerateDecelerateInterpolator())
+                    .setInterpolator(BakedBezierInterpolator.INSTANCE)
                     .withLayer()
                     .start();
         } else {
@@ -190,7 +194,8 @@
                 .translationX(0)
                 .translationY(0)
                 .setStartDelay(235)
-                .setDuration(Constants.Values.TaskView.Animation.TaskIconOnEnterDuration)
+                .setInterpolator(BakedBezierInterpolator.INSTANCE)
+                .setDuration(Utilities.calculateTranslationAnimationDuration(translate))
                 .withLayer()
                 .start();
     }
@@ -206,8 +211,8 @@
             .translationX(translate / 2)
             .translationY(-translate)
             .setStartDelay(0)
-            .setDuration(Constants.Values.TaskView.Animation.TaskIconOnLeavingDuration)
-            .setInterpolator(new DecelerateInterpolator())
+            .setInterpolator(BakedBezierInterpolator.INSTANCE)
+            .setDuration(Utilities.calculateTranslationAnimationDuration(translate))
             .withLayer()
             .withEndAction(r)
             .start();