Enabling filtering by base intent package.

- Toggling recents now unfilters the current stack before dismissing recents

Change-Id: Id0d240bf16bc32957ee7e5a9882a453044903d29
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index b5950e9..2b08141 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -26,7 +26,7 @@
         public static final boolean Verbose = false;
 
         public static class App {
-            public static final boolean EnableTaskFiltering = false;
+            public static final boolean EnableTaskFiltering = true;
             public static final boolean EnableTaskStackClipping = false;
             public static final boolean EnableToggleNewRecentsActivity = false;
             // This disables the bitmap and icon caches to
@@ -81,6 +81,10 @@
             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;
@@ -107,7 +111,7 @@
             public static final boolean AnimateFrontTaskIconOnEnterRecents = true;
             public static final boolean AnimateFrontTaskIconOnLeavingRecents = true;
 
-            public static final boolean UseRoundedCorners = true;
+            public static final boolean UseRoundedCorners = false;
             public static final float RoundedCornerRadiusDps = 3;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index b65b864..dd75921 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -54,8 +54,11 @@
                     finish();
                 }
             } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
-                // Dismiss recents and launch the first task if possible
-                dismissRecentsIfVisible();
+                // Try and unfilter and filtered stacks
+                if (!mRecentsView.unfilterFilteredStacks()) {
+                    // If there are no filtered stacks, dismiss recents and launch the first task
+                    dismissRecentsIfVisible();
+                }
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index 515cec1..22363bb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -66,7 +66,7 @@
                 // in a bottom inset
                 tsv.computeRects(windowRect.width(), windowRect.height() - systemInsets.top, 0);
                 tsv.boundScroll();
-                TaskViewTransform transform = tsv.getStackTransform(0);
+                TaskViewTransform transform = tsv.getStackTransform(0, tsv.getStackScroll());
                 Rect taskRect = new Rect(transform.rect);
 
                 data.putParcelable("taskRect", taskRect);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index b497b69..90c83d0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -190,7 +190,7 @@
                         // Load the icon
                         if (loadIcon == null || forceLoadTask) {
                             PackageManager pm = mContext.getPackageManager();
-                            ActivityInfo info = pm.getActivityInfo(t.key.intent.getComponent(),
+                            ActivityInfo info = pm.getActivityInfo(t.key.baseIntent.getComponent(),
                                     PackageManager.GET_META_DATA);
                             Drawable icon = info.loadIcon(pm);
                             if (!mCancelled) {
@@ -218,7 +218,7 @@
                                 } else {
                                     Console.logError(mContext,
                                             "Failed to load task top thumbnail for: " +
-                                                    t.key.intent.getComponent().getPackageName());
+                                                    t.key.baseIntent.getComponent().getPackageName());
                                 }
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
index 33e4246..9048cba 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
@@ -20,9 +20,6 @@
 
 /* Common code */
 public class Utilities {
-    public static final Rect tmpRect = new Rect();
-    public static final Rect tmpRect2 = new Rect();
-
     /** 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/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index cda4ab2..677334d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -37,11 +37,11 @@
     /* The Task Key represents the unique primary key for the task */
     public static class TaskKey {
         public final int id;
-        public final Intent intent;
+        public final Intent baseIntent;
 
         public TaskKey(int id, Intent intent) {
             this.id = id;
-            this.intent = intent;
+            this.baseIntent = intent;
         }
 
         @Override
@@ -56,7 +56,7 @@
 
         @Override
         public String toString() {
-            return "Task.Key: " + id + ", " + intent.getComponent().getPackageName();
+            return "Task.Key: " + id + ", " + baseIntent.getComponent().getPackageName();
         }
     }
 
@@ -120,6 +120,6 @@
 
     @Override
     public String toString() {
-        return "Task: " + key.intent.getComponent().getPackageName() + " [" + super.toString() + "]";
+        return "Task: " + key.baseIntent.getComponent().getPackageName() + " [" + super.toString() + "]";
     }
 }
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 f2f89ae3..a0e5b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -39,9 +39,11 @@
     TaskFilter mFilter;
 
     /** Sets the task filter, saving the current touch state */
-    void setFilter(TaskFilter filter) {
+    boolean setFilter(TaskFilter filter) {
+        ArrayList<Task> prevFilteredTasks = new ArrayList<Task>(mFilteredTasks);
         mFilter = filter;
         updateFilteredTasks();
+        return !prevFilteredTasks.equals(mFilteredTasks);
     }
 
     /** Removes the task filter and returns the previous touch state */
@@ -126,9 +128,9 @@
         /* Notifies when a task has been removed from the stack */
         public void onStackTaskRemoved(TaskStack stack, Task t);
         /** Notifies when the stack was filtered */
-        public void onStackFiltered(TaskStack stack);
+        public void onStackFiltered(TaskStack newStack, ArrayList<Task> curStack, Task t);
         /** Notifies when the stack was un-filtered */
-        public void onStackUnfiltered(TaskStack stack);
+        public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curStack);
     }
 
     Context mContext;
@@ -201,29 +203,30 @@
     }
 
     /** Filters the stack into tasks similar to the one specified */
-    public void filterTasks(Task t) {
+    public void filterTasks(final Task t) {
+        ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
+
         // Set the task list filter
-        // XXX: This is a dummy filter that currently just accepts every other task.
-        mTaskList.setFilter(new TaskFilter() {
+        boolean filtered = mTaskList.setFilter(new TaskFilter() {
             @Override
-            public boolean acceptTask(Task t, int i) {
-                if (i % 2 == 0) {
-                    return true;
-                }
-                return false;
+            public boolean acceptTask(Task at, int i) {
+                return t.key.baseIntent.getComponent().getPackageName().equals(
+                        at.key.baseIntent.getComponent().getPackageName());
             }
         });
-        if (mCb != null) {
-            mCb.onStackFiltered(this);
+        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);
+            mCb.onStackUnfiltered(this, oldStack);
         }
     }
 
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 cc85439..e89bde5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -211,17 +211,18 @@
                 View sourceView = tv;
                 int offsetX = 0;
                 int offsetY = 0;
+                int stackScroll = stackView.getStackScroll();
                 if (tv == null) {
                     // If there is no actual task view, then use the stack view as the source view
                     // and then offset to the expected transform rect, but bound this to just
                     // outside the display rect (to ensure we don't animate from too far away)
                     RecentsConfiguration config = RecentsConfiguration.getInstance();
                     sourceView = stackView;
-                    transform = stackView.getStackTransform(stack.indexOfTask(task));
+                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
                     offsetX = transform.rect.left;
                     offsetY = Math.min(transform.rect.top, config.displayRect.height());
                 } else {
-                    transform = stackView.getStackTransform(stack.indexOfTask(task));
+                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
                 }
 
                 // Compute the thumbnail to scale up from
@@ -255,7 +256,7 @@
                     }
                 } else {
                     // Launch the activity with the desired animation
-                    Intent i = new Intent(task.key.intent);
+                    Intent i = new Intent(task.key.baseIntent);
                     i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
                             | Intent.FLAG_ACTIVITY_TASK_ON_HOME
                             | Intent.FLAG_ACTIVITY_NEW_TASK);
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 e7f517f..aeb571d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -18,6 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.app.Activity;
@@ -34,6 +35,7 @@
 import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.OverScroller;
+import com.android.systemui.R;
 import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
@@ -41,7 +43,6 @@
 import com.android.systemui.recents.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.R;
 
 import java.util.ArrayList;
 
@@ -71,6 +72,7 @@
     int mStackScroll;
     int mMinScroll;
     int mMaxScroll;
+    int mStashedScroll;
     OverScroller mScroller;
     ObjectAnimator mScrollAnimator;
 
@@ -79,6 +81,9 @@
     int mStackViewsAnimationDuration;
     boolean mStackViewsDirty = true;
     boolean mAwaitingFirstLayout = true;
+    int[] mTmpVisibleRange = new int[2];
+    Rect mTmpRect = new Rect();
+    Rect mTmpRect2 = new Rect();
     LayoutInflater mInflater;
 
     public TaskStackView(Context context, TaskStack stack) {
@@ -102,7 +107,7 @@
     }
     void requestSynchronizeStackViewsWithModel(int duration) {
         Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
-                "[TaskStackView|requestSynchronize]", "", Console.AnsiYellow);
+                "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
         if (!mStackViewsDirty) {
             invalidate();
         }
@@ -128,14 +133,17 @@
     }
 
     /** Update/get the transform */
-    public TaskViewTransform getStackTransform(int indexInStack) {
+    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
         TaskViewTransform transform = new TaskViewTransform();
 
-        // Map the items to an continuous position relative to the current scroll
+        // Return early if we have an invalid index
+        if (indexInStack < 0) return transform;
+
+        // Map the items to an continuous position relative to the specified scroll
         int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
         float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
         float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
-        float t = ((indexInStack * overlapHeight) - getStackScroll()) / overlapHeight;
+        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
         float boundedT = Math.max(t, -(numPeekCards + 1));
 
         // Set the scale relative to its position
@@ -167,25 +175,57 @@
         return transform;
     }
 
+    /**
+     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
+     */
+    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
+                                                            int stackScroll,
+                                                            int[] visibleRangeOut) {
+        // XXX: Optimization: Use binary search to find the visible range
+
+        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
+        int taskCount = tasks.size();
+        int firstVisibleIndex = -1;
+        int lastVisibleIndex = -1;
+        for (int i = 0; i < taskCount; i++) {
+            TaskViewTransform transform = getStackTransform(i, stackScroll);
+            taskTransforms.add(transform);
+            if (transform.visible) {
+                if (firstVisibleIndex < 0) {
+                    firstVisibleIndex = i;
+                }
+                lastVisibleIndex = i;
+            }
+        }
+        if (visibleRangeOut != null) {
+            visibleRangeOut[0] = firstVisibleIndex;
+            visibleRangeOut[1] = lastVisibleIndex;
+        }
+        return taskTransforms;
+    }
+
     /** Synchronizes the views with the model */
     void synchronizeStackViewsWithModel() {
         Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
                 "[TaskStackView|synchronizeViewsWithModel]",
                 "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
         if (mStackViewsDirty) {
-
-            // XXX: Optimization: Use binary search to find the visible range
-            // XXX: Optimize to not call getStackTransform() so many times
             // XXX: Consider using TaskViewTransform pool to prevent allocations
             // XXX: Iterate children views, update transforms and remove all that are not visible
             //      For all remaining tasks, update transforms and if visible add the view
 
-            // Update the visible state of all the tasks
+            // Get all the task transforms
+            int[] visibleRange = mTmpVisibleRange;
+            int stackScroll = getStackScroll();
             ArrayList<Task> tasks = mStack.getTasks();
+            ArrayList<TaskViewTransform> taskTransforms = getStackTransforms(tasks, stackScroll,
+                    visibleRange);
+
+            // Update the visible state of all the tasks
             int taskCount = tasks.size();
             for (int i = 0; i < taskCount; i++) {
                 Task task = tasks.get(i);
-                TaskViewTransform transform = getStackTransform(i);
+                TaskViewTransform transform = taskTransforms.get(i);
                 TaskView tv = getChildViewForTask(task);
 
                 if (transform.visible) {
@@ -194,11 +234,9 @@
                         // When we are picking up a new view from the view pool, prepare it for any
                         // following animation by putting it in a reasonable place
                         if (mStackViewsAnimationDuration > 0 && i != 0) {
-                            // XXX: We have to animate when filtering, etc. Maybe we should have a
-                            //      runnable that ensures that tasks are animated in a special way
-                            //      when they are entering the scene?
                             int fromIndex = (transform.t < 0) ? (i - 1) : (i + 1);
-                            tv.updateViewPropertiesFromTask(null, getStackTransform(fromIndex), 0);
+                            tv.updateViewPropertiesToTaskTransform(null,
+                                    getStackTransform(fromIndex, stackScroll), 0);
                         }
                     }
                 } else {
@@ -208,17 +246,18 @@
                 }
             }
 
-            // Update all the current view children
+            // Update all the remaining view children
             // NOTE: We have to iterate in reverse where because we are removing views directly
             int childCount = getChildCount();
             for (int i = childCount - 1; i >= 0; i--) {
                 TaskView tv = (TaskView) getChildAt(i);
                 Task task = tv.getTask();
-                TaskViewTransform transform = getStackTransform(mStack.indexOfTask(task));
-                if (!transform.visible) {
+                int taskIndex = mStack.indexOfTask(task);
+                if (taskIndex < 0 || !taskTransforms.get(taskIndex).visible) {
                     mViewPool.returnViewToPool(tv);
                 } else {
-                    tv.updateViewPropertiesFromTask(null, transform, mStackViewsAnimationDuration);
+                    tv.updateViewPropertiesToTaskTransform(null, taskTransforms.get(taskIndex),
+                            mStackViewsAnimationDuration);
                 }
             }
 
@@ -235,6 +274,10 @@
         mStackScroll = value;
         requestSynchronizeStackViewsWithModel();
     }
+    /** Sets the current stack scroll without synchronizing the stack view with the model */
+    public void setStackScrollRaw(int value) {
+        mStackScroll = value;
+    }
 
     /** Gets the current stack scroll */
     public int getStackScroll() {
@@ -251,36 +294,39 @@
 
             // Abort any current animations
             abortScroller();
-            if (mScrollAnimator != null) {
-                mScrollAnimator.cancel();
-                mScrollAnimator.removeAllListeners();
-            }
+            abortBoundScrollAnimation();
 
             // Start a new scroll animation
-            mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
-            mScrollAnimator.setDuration(duration);
-            mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    setStackScroll((Integer) animation.getAnimatedValue());
-                }
-            });
-            mScrollAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Disable hw layers on the stack
-                    decHwLayersRefCount("animateBoundScroll");
-                }
-            });
+            animateScroll(curScroll, newScroll, duration);
             mScrollAnimator.start();
         }
         return mScrollAnimator;
     }
 
+    /** Animates the stack scroll */
+    void animateScroll(int curScroll, int newScroll, int duration) {
+        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
+        mScrollAnimator.setDuration(duration);
+        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                setStackScroll((Integer) animation.getAnimatedValue());
+            }
+        });
+        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Disable hw layers on the stack
+                decHwLayersRefCount("animateBoundScroll");
+            }
+        });
+    }
+
     /** Aborts any current stack scrolls */
     void abortBoundScrollAnimation() {
         if (mScrollAnimator != null) {
             mScrollAnimator.cancel();
+            mScrollAnimator.removeAllListeners();
         }
     }
 
@@ -304,6 +350,20 @@
         return false;
     }
 
+    /**
+     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
+     * model.
+     */
+    public boolean boundScrollRaw() {
+        int curScroll = getStackScroll();
+        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
+        if (newScroll != curScroll) {
+            setStackScrollRaw(newScroll);
+            return true;
+        }
+        return false;
+    }
+
     /** Returns whether the current scroll is out of bounds */
     boolean isScrollOutOfBounds() {
         return (getStackScroll() < 0) || (getStackScroll() > mMaxScroll);
@@ -404,12 +464,12 @@
             TaskView tv = (TaskView) child;
             TaskView nextTv = null;
             int curIndex = indexOfChild(tv);
-            if (curIndex < (getChildCount() - 1)) {
+            if ((curIndex > -1) && (curIndex < (getChildCount() - 1))) {
                 // Clip against the next view (if we aren't animating its alpha)
                 nextTv = (TaskView) getChildAt(curIndex + 1);
                 if (nextTv.getAlpha() == 1f) {
-                    Rect curRect = tv.getClippingRect(Utilities.tmpRect, false);
-                    Rect nextRect = nextTv.getClippingRect(Utilities.tmpRect2, true);
+                    Rect curRect = tv.getClippingRect(mTmpRect, false);
+                    Rect nextRect = nextTv.getClippingRect(mTmpRect2, true);
                     RecentsConfiguration config = RecentsConfiguration.getInstance();
                     // The hit rects are relative to the task view, which needs to be offset by the
                     // system bar height
@@ -528,9 +588,7 @@
                     mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
         }
 
-        if (!mAwaitingFirstLayout) {
-            requestSynchronizeStackViewsWithModel();
-        } else {
+        if (mAwaitingFirstLayout) {
             mAwaitingFirstLayout = false;
         }
     }
@@ -570,13 +628,185 @@
     }
 
     @Override
-    public void onStackFiltered(TaskStack stack) {
-        requestSynchronizeStackViewsWithModel();
+    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curStack,
+                                Task filteredTask) {
+        // NOTE: This code assumes that the current (unfiltered) stack is a superset of the new
+        // (filtered) stack
+        // XXX: Use HW Layers
+
+        // Stash the scroll 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);
+
+        // Bound the new stack scroll
+        updateMinMaxScroll(false);
+        boundScrollRaw();
+
+        // Compute the transforms of the items in the new stack
+        final ArrayList<TaskViewTransform> taskTransforms =
+                getStackTransforms(mStack.getTasks(), getStackScroll(), null);
+
+        // 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
+        final ArrayList<TaskView> childrenToReturnToPool = new ArrayList<TaskView>();
+        final ArrayList<Task> tasks = mStack.getTasks();
+        ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
+        int childCount = getChildCount();
+        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
+                TaskViewTransform fromTransform = curTaskTransforms.get(curStack.indexOf(task));
+                toTransform = new TaskViewTransform(fromTransform);
+                tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
+                tv.prepareTaskTransformForFilterTaskHidden(toTransform);
+
+                childrenToReturnToPool.add(tv);
+            } else {
+                toTransform = taskTransforms.get(taskIndex);
+            }
+            childViewAnims.add(tv.getAnimatorToTaskTransform(toTransform));
+        }
+
+        AnimatorSet childViewAnimSet = new AnimatorSet();
+        childViewAnimSet.setDuration(
+                Constants.Values.TaskStackView.Animation.FilteredCurrentViewsDuration);
+        childViewAnimSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // 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
+                int taskCount = tasks.size();
+                for (int i = 0; i < taskCount; i++) {
+                    Task task = tasks.get(i);
+                    TaskViewTransform toTransform = taskTransforms.get(i);
+                    if (toTransform.visible) {
+                        TaskViewTransform fromTransform =
+                                curTaskTransforms.get(curStack.indexOf(task));
+                        TaskView tv = getChildViewForTask(task);
+                        if (tv == null) {
+                            tv = mViewPool.pickUpViewFromPool(task, task);
+
+                            // Animate from the current position to the new position
+                            tv.prepareTaskTransformForFilterTaskVisible(fromTransform);
+                            tv.updateViewPropertiesToTaskTransform(fromTransform,
+                                    toTransform,
+                                    Constants.Values.TaskStackView.Animation.FilteredNewViewsDuration);
+                        }
+                    }
+                }
+                invalidate();
+            }
+        });
+        childViewAnimSet.playTogether(childViewAnims);
+        childViewAnimSet.start();
     }
 
     @Override
-    public void onStackUnfiltered(TaskStack stack) {
-        requestSynchronizeStackViewsWithModel();
+    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curStack) {
+        // Compute the transforms of the items in the current stack
+        final int curScroll = getStackScroll();
+        final ArrayList<TaskViewTransform> curTaskTransforms =
+                getStackTransforms(curStack, curScroll, null);
+
+        // Restore the stashed scroll
+        updateMinMaxScroll(false);
+        setStackScrollRaw(mStashedScroll);
+        boundScrollRaw();
+
+        // Compute the transforms of the items in the new stack
+        final ArrayList<TaskViewTransform> taskTransforms =
+                getStackTransforms(mStack.getTasks(), getStackScroll(), null);
+
+        // 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
+        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
+        final ArrayList<Task> tasks = mStack.getTasks();
+        ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            Task task = tv.getTask();
+            int taskIndex = tasks.indexOf(task);
+            TaskViewTransform transform;
+
+            // 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);
+                childrenToRemove.add(tv);
+            } else {
+                transform = taskTransforms.get(taskIndex);
+            }
+            childViewAnims.add(tv.getAnimatorToTaskTransform(transform));
+        }
+
+        AnimatorSet childViewAnimSet = new AnimatorSet();
+        childViewAnimSet.setDuration(
+                Constants.Values.TaskStackView.Animation.UnfilteredCurrentViewsDuration);
+        childViewAnimSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Return all the removed children to the view pool
+                for (TaskView tv : childrenToRemove) {
+                    mViewPool.returnViewToPool(tv);
+                }
+
+                // Increment the hw layers ref count
+                addHwLayersRefCount("unfilteredNewViews");
+
+                // 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 offset = 0;
+                for (int i = 0; i < taskCount; i++) {
+                    Task task = tasks.get(i);
+                    TaskViewTransform toTransform = taskTransforms.get(i);
+                    if (toTransform.visible) {
+                        TaskView tv = getChildViewForTask(task);
+                        if (tv == null) {
+                            // For views that are not already visible, animate them in
+                            tv = mViewPool.pickUpViewFromPool(task, task);
+
+                            // Animate in this new view
+                            TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
+                            tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
+                            tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
+                            newViewAnims.add(tv.getAnimatorToTaskTransform(toTransform));
+                            offset++;
+                        }
+                    }
+                }
+
+                // Run the animation
+                newViewAnimSet.setDuration(
+                        Constants.Values.TaskStackView.Animation.UnfilteredNewViewsDuration);
+                newViewAnimSet.playTogether(newViewAnims);
+                newViewAnimSet.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        // Decrement the hw layers ref count
+                        decHwLayersRefCount("unfilteredNewViews");
+                    }
+                });
+                newViewAnimSet.start();
+
+                invalidate();
+            }
+        });
+        childViewAnimSet.playTogether(childViewAnims);
+        childViewAnimSet.start();
     }
 
     /**** ViewPoolConsumer Implementation ****/
@@ -845,7 +1075,7 @@
 
     /** Handles touch events once we have intercepted them */
     public boolean onTouchEvent(MotionEvent ev) {
-        Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
+        Console.log(Constants.DebugFlags.UI.TouchEvents,
                 "[TaskStackViewTouchHandler|touchEvent]",
                 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
 
@@ -1045,9 +1275,17 @@
                     ActivityManager.REMOVE_TASK_KILL_PROCESS);
         }
 
-        // If there are no remaining tasks, then just close the activity
+        // If there are no remaining tasks, then either unfilter the current stack, or just close
+        // the activity if there are no filtered stacks
         if (mSv.mStack.getTaskCount() == 0) {
-            activity.finish();
+            boolean shouldFinishActivity = true;
+            if (mSv.mStack.hasFilteredTasks()) {
+                mSv.mStack.unfilterTasks();
+                shouldFinishActivity = (mSv.mStack.getTaskCount() == 0);
+            }
+            if (shouldFinishActivity) {
+                activity.finish();
+            }
         }
 
         // Disable HW layers
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 c86b0e1..e04a1b2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Path;
@@ -72,6 +75,7 @@
         // Bind the views
         mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
         mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
+        mBarView.mActivityIcon.setOnClickListener(this);
         if (mTaskDataLoaded) {
             onTaskDataLoaded(false);
         }
@@ -90,12 +94,16 @@
     }
 
     @Override
-    protected void onDraw(Canvas canvas) {
+    protected void dispatchDraw(Canvas canvas) {
+        int restoreCount = 0;
         if (Constants.Values.TaskView.UseRoundedCorners) {
+            restoreCount = canvas.save();
             canvas.clipPath(mRoundedRectClipPath);
         }
-
-        super.onDraw(canvas);
+        super.dispatchDraw(canvas);
+        if (Constants.Values.TaskView.UseRoundedCorners) {
+            canvas.restoreToCount(restoreCount);
+        }
     }
 
     /** Set callback */
@@ -109,27 +117,43 @@
     }
 
     /** Synchronizes this view's properties with the task's transform */
-    void updateViewPropertiesFromTask(TaskViewTransform animateFromTransform,
-                                      TaskViewTransform transform, int duration) {
+    void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
+                                             TaskViewTransform toTransform, int duration) {
         if (duration > 0) {
             if (animateFromTransform != null) {
                 setTranslationY(animateFromTransform.translationY);
                 setScaleX(animateFromTransform.scale);
                 setScaleY(animateFromTransform.scale);
+                setAlpha(animateFromTransform.alpha);
             }
-            animate().translationY(transform.translationY)
-                    .scaleX(transform.scale)
-                    .scaleY(transform.scale)
+            animate().translationY(toTransform.translationY)
+                    .scaleX(toTransform.scale)
+                    .scaleY(toTransform.scale)
+                    .alpha(toTransform.alpha)
                     .setDuration(duration)
                     .setInterpolator(new AccelerateDecelerateInterpolator())
+                    .withLayer()
                     .start();
         } else {
-            setTranslationY(transform.translationY);
-            setScaleX(transform.scale);
-            setScaleY(transform.scale);
+            setTranslationY(toTransform.translationY);
+            setScaleX(toTransform.scale);
+            setScaleY(toTransform.scale);
+            setAlpha(toTransform.alpha);
         }
     }
 
+    /** Returns an animator to animate this task to the specified transform */
+    Animator getAnimatorToTaskTransform(TaskViewTransform toTransform) {
+        AnimatorSet anims = new AnimatorSet();
+        anims.playTogether(
+                ObjectAnimator.ofFloat(this, "translationY", toTransform.translationY),
+                ObjectAnimator.ofFloat(this, "scaleX", toTransform.scale),
+                ObjectAnimator.ofFloat(this, "scaleY", toTransform.scale),
+                ObjectAnimator.ofFloat(this, "alpha", toTransform.alpha)
+        );
+        return anims;
+    }
+
     /** Resets this view's properties */
     void resetViewProperties() {
         setTranslationX(0f);
@@ -139,6 +163,17 @@
         setAlpha(1f);
     }
 
+    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
+        // Fade the view out and slide it away
+        toTransform.alpha = 0f;
+        toTransform.translationY += 200;
+    }
+
+    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
+        // Fade the view in
+        fromTransform.alpha = 0f;
+    }
+
     /** Animates this task view as it enters recents */
     public void animateOnEnterRecents() {
         RecentsConfiguration config = RecentsConfiguration.getInstance();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 66c52a0..0748bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -23,13 +23,27 @@
 public class TaskViewTransform {
     public int translationY = 0;
     public float scale = 1f;
-    public boolean visible = true;
+    public float alpha = 1f;
+    public boolean visible = false;
     public Rect rect = new Rect();
     float t;
 
+    public TaskViewTransform() {
+        // Do nothing
+    }
+
+    public TaskViewTransform(TaskViewTransform o) {
+        translationY = o.translationY;
+        scale = o.scale;
+        alpha = o.alpha;
+        visible = o.visible;
+        rect.set(o.rect);
+        t = o.t;
+    }
+
     @Override
     public String toString() {
-        return "TaskViewTransform y: " + translationY + " scale: " + scale +
+        return "TaskViewTransform y: " + translationY + " scale: " + scale + " alpha: " + alpha +
                 " visible: " + visible + " rect: " + rect;
     }
 }