Refactoring to support groups.

- Removing RecentService, determining animations just in time
- Fixing a few issues with animations of newly picked up tasks from the pool
- Moving helper classes into sub package

Change-Id: Ie10385d1f9ca79eea918b16932f56b60e2802304
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
index 1b215c9..3e2ef94 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -40,7 +40,8 @@
 import android.view.View;
 
 import com.android.systemui.R;
-import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 import java.util.ArrayList;
@@ -200,7 +201,7 @@
         final ActivityManager am = (ActivityManager)
                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
         final PackageManager pm = mContext.getPackageManager();
-        final Bitmap thumbnail = Utilities.getThumbnail(am, td.persistentTaskId);
+        final Bitmap thumbnail = SystemServicesProxy.getThumbnail(am, td.persistentTaskId);
         Drawable icon = getFullResIcon(td.resolveInfo, pm);
         if (td.userId != UserHandle.myUserId()) {
             // Need to badge the icon
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 52ccb59..3c4f030 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -22,126 +22,43 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.view.View;
-import android.view.WindowManager;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskViewTransform;
 
-import java.lang.ref.WeakReference;
-import java.util.Iterator;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /** A proxy implementation for the recents component */
 public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
 
-    /** A handler for messages from the recents implementation */
-    class RecentsMessageHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) {
-                Resources res = mContext.getResources();
-                float statusBarHeight = res.getDimensionPixelSize(
-                        com.android.internal.R.dimen.status_bar_height);
-                Bundle replyData = msg.getData().getParcelable(KEY_CONFIGURATION_DATA);
-                mSingleCountFirstTaskRect = replyData.getParcelable(KEY_SINGLE_TASK_STACK_RECT);
-                mSingleCountFirstTaskRect.offset(0, (int) statusBarHeight);
-                mTwoCountFirstTaskRect = replyData.getParcelable(KEY_TWO_TASK_STACK_RECT);
-                mTwoCountFirstTaskRect.offset(0, (int) statusBarHeight);
-                mMultipleCountFirstTaskRect = replyData.getParcelable(KEY_MULTIPLE_TASK_STACK_RECT);
-                mMultipleCountFirstTaskRect.offset(0, (int) statusBarHeight);
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.RecentsComponent,
-                            "[RecentsComponent|RecentsMessageHandler|handleMessage]",
-                            "singleTaskRect: " + mSingleCountFirstTaskRect +
-                            " twoTaskRect: " + mTwoCountFirstTaskRect +
-                            " multipleTaskRect: " + mMultipleCountFirstTaskRect);
-                }
-
-                // If we had the update the animation rects as a result of onServiceConnected, then
-                // we check for whether we need to toggle the recents here.
-                if (mToggleRecentsUponServiceBound) {
-                    startRecentsActivity();
-                    mToggleRecentsUponServiceBound = false;
-                }
-            }
-        }
-    }
-
-    /** A service connection to the recents implementation */
-    class RecentsServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (Console.Enabled) {
-                Console.log(Constants.Log.App.RecentsComponent,
-                        "[RecentsComponent|ServiceConnection|onServiceConnected]",
-                        "toggleRecents: " + mToggleRecentsUponServiceBound);
-            }
-            mService = new Messenger(service);
-            mServiceIsBound = true;
-
-            if (hasValidTaskRects()) {
-                // Start recents if this new service connection was triggered by hitting recents
-                if (mToggleRecentsUponServiceBound) {
-                    startRecentsActivity();
-                    mToggleRecentsUponServiceBound = false;
-                }
-            } else {
-                // Otherwise, update the animation rects before starting the recents if requested
-                updateAnimationRects();
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            if (Console.Enabled) {
-                Console.log(Constants.Log.App.RecentsComponent,
-                        "[RecentsComponent|ServiceConnection|onServiceDisconnected]");
-            }
-            mService = null;
-            mServiceIsBound = false;
-        }
-    }
-
-    final public static int MSG_UPDATE_FOR_CONFIGURATION = 0;
-    final public static int MSG_UPDATE_TASK_THUMBNAIL = 1;
-    final public static int MSG_PRELOAD_TASKS = 2;
-    final public static int MSG_CANCEL_PRELOAD_TASKS = 3;
-    final public static int MSG_SHOW_RECENTS = 4;
-    final public static int MSG_HIDE_RECENTS = 5;
-    final public static int MSG_TOGGLE_RECENTS = 6;
-    final public static int MSG_START_ENTER_ANIMATION = 7;
-
     final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome";
     final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail";
     final public static String EXTRA_FROM_APP_FULL_SCREENSHOT = "recents.thumbnail";
     final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
-    final public static String KEY_CONFIGURATION_DATA = "recents.data.updateForConfiguration";
-    final public static String KEY_WINDOW_RECT = "recents.windowRect";
-    final public static String KEY_SYSTEM_INSETS = "recents.systemInsets";
-    final public static String KEY_SINGLE_TASK_STACK_RECT = "recents.singleCountTaskRect";
-    final public static String KEY_TWO_TASK_STACK_RECT = "recents.twoCountTaskRect";
-    final public static String KEY_MULTIPLE_TASK_STACK_RECT = "recents.multipleCountTaskRect";
+    final public static String EXTRA_TRIGGERED_FROM_TASK_ID = "recents.activeTaskId";
 
     final static int sMinToggleDelay = 425;
 
     final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
     final static String sRecentsPackage = "com.android.systemui";
     final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
-    final static String sRecentsService = "com.android.systemui.recents.RecentsService";
 
     static Bitmap sLastScreenshot;
     static RecentsComponent.Callbacks sRecentsComponentCallbacks;
@@ -150,37 +67,37 @@
     SystemServicesProxy mSystemServicesProxy;
 
     // Recents service binding
-    Messenger mService = null;
-    Messenger mMessenger;
-    RecentsMessageHandler mHandler;
+    Handler mHandler;
     boolean mBootCompleted = false;
-    boolean mServiceIsBound = false;
-    boolean mToggleRecentsUponServiceBound;
-    RecentsServiceConnection mConnection = new RecentsServiceConnection();
+
+    // Task launching
+    RecentsConfiguration mConfig;
+    Rect mWindowRect;
+    Rect mTaskStackBounds;
+    TaskViewTransform mTmpTransform = new TaskViewTransform();
+    int mStatusBarHeight;
 
     // Variables to keep track of if we need to start recents after binding
     View mStatusBarView;
     boolean mTriggeredFromAltTab;
-
-    Rect mSingleCountFirstTaskRect = new Rect();
-    Rect mTwoCountFirstTaskRect = new Rect();
-    Rect mMultipleCountFirstTaskRect = new Rect();
     long mLastToggleTime;
 
     public AlternateRecentsComponent(Context context) {
         mContext = context;
         mSystemServicesProxy = new SystemServicesProxy(context);
-        mHandler = new RecentsMessageHandler();
-        mMessenger = new Messenger(mHandler);
+        mHandler = new Handler();
+        mConfig = RecentsConfiguration.reinitialize(context);
+        mWindowRect = mSystemServicesProxy.getWindowRect();
+        mTaskStackBounds = new Rect();
+        mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mTaskStackBounds);
+        mStatusBarHeight = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
     }
 
     public void onStart() {
         if (Console.Enabled) {
             Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|start]");
         }
-
-        // Try to create a long-running connection to the recents service
-        bindToRecentsService(false);
     }
 
     public void onBootCompleted() {
@@ -194,12 +111,6 @@
         }
         mStatusBarView = statusBarView;
         mTriggeredFromAltTab = triggeredFromAltTab;
-        if (!mServiceIsBound) {
-            // Try to create a long-running connection to the recents service before toggling
-            // recents
-            bindToRecentsService(true);
-            return;
-        }
 
         try {
             startRecentsActivity();
@@ -214,18 +125,14 @@
             Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]");
         }
 
-        if (mServiceIsBound && mBootCompleted) {
-            if (isRecentsTopMost(null)) {
-                // Notify recents to close it
-                try {
-                    Bundle data = new Bundle();
-                    Message msg = Message.obtain(null, MSG_HIDE_RECENTS,
-                            triggeredFromAltTab ? 1 : 0, 0);
-                    msg.setData(data);
-                    mService.send(msg);
-                } catch (RemoteException re) {
-                    re.printStackTrace();
-                }
+        if (mBootCompleted) {
+            if (isRecentsTopMost(getTopMostTask(), null)) {
+                // Notify recents to hide itself
+                Intent intent = new Intent(RecentsActivity.ACTION_HIDE_RECENTS_ACTIVITY);
+                intent.setPackage(mContext.getPackageName());
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                intent.putExtra(RecentsActivity.EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+                mContext.sendBroadcast(intent);
             }
         }
     }
@@ -237,17 +144,10 @@
                     Constants.Log.App.TimeRecentsStartupKey);
             Console.logStartTracingTime(Constants.Log.App.TimeRecentsLaunchTask,
                     Constants.Log.App.TimeRecentsLaunchKey);
-            Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|toggleRecents]",
-                    "serviceIsBound: " + mServiceIsBound);
+            Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|toggleRecents]", "");
         }
         mStatusBarView = statusBarView;
         mTriggeredFromAltTab = false;
-        if (!mServiceIsBound) {
-            // Try to create a long-running connection to the recents service before toggling
-            // recents
-            bindToRecentsService(true);
-            return;
-        }
 
         try {
             toggleRecentsActivity();
@@ -265,94 +165,26 @@
     }
 
     public void onConfigurationChanged(Configuration newConfig) {
-        updateAnimationRects();
+        mConfig = RecentsConfiguration.reinitialize(mContext);
+        mWindowRect = mSystemServicesProxy.getWindowRect();
+        mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mTaskStackBounds);
+        sLastScreenshot = null;
     }
 
-    /** Binds to the recents implementation */
-    private void bindToRecentsService(boolean toggleRecentsUponConnection) {
-        mToggleRecentsUponServiceBound = toggleRecentsUponConnection;
-        Intent intent = new Intent();
-        intent.setClassName(sRecentsPackage, sRecentsService);
-        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
-    }
-
-    /** Returns whether we have valid task rects to animate to. */
-    boolean hasValidTaskRects() {
-        return mSingleCountFirstTaskRect != null && mSingleCountFirstTaskRect.width() > 0 &&
-                mSingleCountFirstTaskRect.height() > 0 && mTwoCountFirstTaskRect != null &&
-                mTwoCountFirstTaskRect.width() > 0 && mTwoCountFirstTaskRect.height() > 0 &&
-                mMultipleCountFirstTaskRect != null && mMultipleCountFirstTaskRect.width() > 0 &&
-                mMultipleCountFirstTaskRect.height() > 0;
-    }
-
-    /** Updates each of the task animation rects. */
-    void updateAnimationRects() {
-        if (mServiceIsBound) {
-            Resources res = mContext.getResources();
-            int statusBarHeight = res.getDimensionPixelSize(
-                    com.android.internal.R.dimen.status_bar_height);
-            int navBarHeight = res.getDimensionPixelSize(
-                    com.android.internal.R.dimen.navigation_bar_height);
-            Rect rect = new Rect();
-            WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-            wm.getDefaultDisplay().getRectSize(rect);
-
-            // Try and update the recents configuration
-            try {
-                Bundle data = new Bundle();
-                data.putParcelable(KEY_WINDOW_RECT, rect);
-                data.putParcelable(KEY_SYSTEM_INSETS, new Rect(0, statusBarHeight, 0, 0));
-                Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0);
-                msg.setData(data);
-                msg.replyTo = mMessenger;
-                mService.send(msg);
-            } catch (RemoteException re) {
-                re.printStackTrace();
-            }
-        }
-    }
-
-    /** Loads the first task thumbnail */
-    Bitmap loadFirstTaskThumbnail() {
+    /** Gets the top task. */
+    ActivityManager.RunningTaskInfo getTopMostTask() {
         SystemServicesProxy ssp = mSystemServicesProxy;
         List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1);
-
-        for (ActivityManager.RunningTaskInfo t : tasks) {
-            return ssp.getTaskThumbnail(t.id);
+        if (!tasks.isEmpty()) {
+            return tasks.get(0);
         }
         return null;
     }
 
-    /** Returns the proper rect to use for the animation, given the number of tasks. */
-    Rect getAnimationTaskRect(List<ActivityManager.RecentTaskInfo> tasks) {
-        // NOTE: Currently there's no method to get the number of non-home tasks, so we have to
-        // compute this ourselves
-        SystemServicesProxy ssp = mSystemServicesProxy;
-        Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
-        while (iter.hasNext()) {
-            ActivityManager.RecentTaskInfo t = iter.next();
-
-            // Skip tasks in the home stack
-            if (ssp.isInHomeStack(t.persistentId)) {
-                iter.remove();
-                continue;
-            }
-        }
-        if (tasks.size() <= 1) {
-            return mSingleCountFirstTaskRect;
-        } else if (tasks.size() <= 2) {
-            return mTwoCountFirstTaskRect;
-        } else {
-            return mMultipleCountFirstTaskRect;
-        }
-    }
-
     /** Returns whether the recents is currently running */
-    boolean isRecentsTopMost(AtomicBoolean isHomeTopMost) {
+    boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost) {
         SystemServicesProxy ssp = mSystemServicesProxy;
-        List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1);
-        if (!tasks.isEmpty()) {
-            ActivityManager.RunningTaskInfo topTask = tasks.get(0);
+        if (topTask != null) {
             ComponentName topActivity = topTask.topActivity;
 
             // Check if the front most activity is recents
@@ -382,39 +214,37 @@
 
         // If Recents is the front most activity, then we should just communicate with it directly
         // to launch the first task or dismiss itself
+        ActivityManager.RunningTaskInfo topTask = getTopMostTask();
         AtomicBoolean isTopTaskHome = new AtomicBoolean();
-        if (isRecentsTopMost(isTopTaskHome)) {
-            // Notify recents to close itself
-            try {
-                Bundle data = new Bundle();
-                Message msg = Message.obtain(null, MSG_TOGGLE_RECENTS, 0, 0);
-                msg.setData(data);
-                mService.send(msg);
+        if (isRecentsTopMost(topTask, isTopTaskHome)) {
+            // Notify recents to toggle itself
+            Intent intent = new Intent(RecentsActivity.ACTION_TOGGLE_RECENTS_ACTIVITY);
+            intent.setPackage(mContext.getPackageName());
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendBroadcast(intent);
 
-                // Time this path
-                if (Console.Enabled) {
-                    Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                            Constants.Log.App.TimeRecentsStartupKey, "sendToggleRecents");
-                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                            Constants.Log.App.TimeRecentsLaunchKey, "sendToggleRecents");
-                }
-            } catch (RemoteException re) {
-                re.printStackTrace();
+            // Time this path
+            if (Console.Enabled) {
+                Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                        Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
+                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                        Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
             }
             mLastToggleTime = System.currentTimeMillis();
             return;
         } else {
             // Otherwise, start the recents activity
-            startRecentsActivity(isTopTaskHome.get());
+            startRecentsActivity(topTask, isTopTaskHome.get());
         }
     }
 
     /** Starts the recents activity if it is not already running */
     void startRecentsActivity() {
         // Check if the top task is in the home stack, and start the recents activity
+        ActivityManager.RunningTaskInfo topTask = getTopMostTask();
         AtomicBoolean isTopTaskHome = new AtomicBoolean();
-        if (!isRecentsTopMost(isTopTaskHome)) {
-            startRecentsActivity(isTopTaskHome.get());
+        if (!isRecentsTopMost(topTask, isTopTaskHome)) {
+            startRecentsActivity(topTask, isTopTaskHome.get());
         }
     }
 
@@ -422,8 +252,6 @@
      * Creates the activity options for a unknown state->recents transition.
      */
     ActivityOptions getUnknownTransitionActivityOptions() {
-        // Reset the last screenshot
-        consumeLastScreenshot();
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_unknown_enter,
                 R.anim.recents_from_unknown_exit, mHandler, this);
@@ -433,8 +261,6 @@
      * Creates the activity options for a home->recents transition.
      */
     ActivityOptions getHomeTransitionActivityOptions() {
-        // Reset the last screenshot
-        consumeLastScreenshot();
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_launcher_enter,
                 R.anim.recents_from_launcher_exit, mHandler, this);
@@ -444,13 +270,13 @@
      * Creates the activity options for an app->recents transition.  If this method sets the static
      * screenshot, then we will use that for the transition.
      */
-    ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) {
+    ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask) {
         // Recycle the last screenshot
         consumeLastScreenshot();
 
         // Take the full screenshot
         if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
-            sLastScreenshot = mSystemServicesProxy.takeScreenshot();
+            sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
             if (sLastScreenshot != null) {
                 return ActivityOptions.makeCustomAnimation(mContext,
                         R.anim.recents_from_app_enter,
@@ -459,8 +285,10 @@
         }
 
         // If the screenshot fails, then load the first task thumbnail and use that
-        Bitmap firstThumbnail = loadFirstTaskThumbnail();
+        Bitmap firstThumbnail = mSystemServicesProxy.getTaskThumbnail(topTask.id);
         if (firstThumbnail != null) {
+            Rect taskRect = getThumbnailTransitionRect(topTask.id);
+
             // Create the new thumbnail for the animation down
             // XXX: We should find a way to optimize this so we don't need to create a new bitmap
             Bitmap thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
@@ -480,8 +308,46 @@
         return getUnknownTransitionActivityOptions();
     }
 
+    /** Returns the transition rect for the given task id. */
+    Rect getThumbnailTransitionRect(int runningTaskId) {
+        // Get the stack of tasks that we are animating into
+        TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy);
+        TaskStackView tsv = new TaskStackView(mContext, stack);
+        TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
+        tsv.computeRects(mTaskStackBounds.width(), mTaskStackBounds.height() - mStatusBarHeight, 0, 0);
+        tsv.setStackScrollToInitialState();
+
+        // Find the running task in the TaskStack
+        Task task = null;
+        ArrayList<Task> tasks = stack.getTasks();
+        if (runningTaskId != -1) {
+            // Otherwise, try and find the task with the
+            int taskCount = tasks.size();
+            for (int i = taskCount - 1; i >= 0; i--) {
+                Task t = tasks.get(i);
+                if (t.key.id == runningTaskId) {
+                    task = t;
+                    break;
+                }
+            }
+        }
+        if (task == null) {
+            // If no task is specified or we can not find the task just use the front most one
+            task = tasks.get(tasks.size() - 1);
+        }
+
+        // Get the transform for the running task
+        TaskStack.GroupTaskIndex groupTaskIndex = new TaskStack.GroupTaskIndex();
+        stack.getGroupIndexForTask(task, groupTaskIndex);
+        mTmpTransform = algo.getStackTransform(groupTaskIndex.groupIndex, groupTaskIndex.taskIndex,
+                tsv.getStackScroll(), mTmpTransform);
+        mTmpTransform.rect.offset(mTaskStackBounds.left, mTaskStackBounds.top);
+        mTmpTransform.rect.offset(0, mStatusBarHeight);
+        return new Rect(mTmpTransform.rect);
+    }
+
     /** Starts the recents activity */
-    void startRecentsActivity(boolean isTopTaskHome) {
+    void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
         // If Recents is not the front-most activity and we should animate into it.  If
         // the activity at the root of the top task stack in the home stack, then we just do a
         // simple transition.  Otherwise, we animate to the rects defined by the Recents service,
@@ -489,34 +355,34 @@
         SystemServicesProxy ssp = mSystemServicesProxy;
         List<ActivityManager.RecentTaskInfo> recentTasks =
                 ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier());
-        Rect taskRect = getAnimationTaskRect(recentTasks);
-        boolean useThumbnailTransition = !isTopTaskHome &&
-                hasValidTaskRects();
+        boolean useThumbnailTransition = !isTopTaskHome;
         boolean hasRecentTasks = !recentTasks.isEmpty();
 
         if (useThumbnailTransition) {
             // Try starting with a thumbnail transition
-            ActivityOptions opts = getThumbnailTransitionActivityOptions(taskRect);
+            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask);
             if (opts != null) {
                 if (sLastScreenshot != null) {
-                    startAlternateRecentsActivity(opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
+                    startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
                 } else {
-                    startAlternateRecentsActivity(opts, EXTRA_FROM_APP_THUMBNAIL);
+                    startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL);
                 }
             } else {
                 // Fall through below to the non-thumbnail transition
                 useThumbnailTransition = false;
             }
-        } else {
+        }
+
+        if (!useThumbnailTransition) {
             // If there is no thumbnail transition, but is launching from home into recents, then
             // use a quick home transition and do the animation from home
             if (hasRecentTasks) {
                 ActivityOptions opts = getHomeTransitionActivityOptions();
-                startAlternateRecentsActivity(opts, EXTRA_FROM_HOME);
+                startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME);
             } else {
                 // Otherwise we do the normal fade from an unknown source
                 ActivityOptions opts = getUnknownTransitionActivityOptions();
-                startAlternateRecentsActivity(opts, null);
+                startAlternateRecentsActivity(topTask, opts, null);
             }
         }
 
@@ -528,7 +394,8 @@
     }
 
     /** Starts the recents activity */
-    void startAlternateRecentsActivity(ActivityOptions opts, String extraFlag) {
+    void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+                                       ActivityOptions opts, String extraFlag) {
         Intent intent = new Intent(sToggleRecentsAction);
         intent.setClassName(sRecentsPackage, sRecentsActivity);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -538,6 +405,7 @@
             intent.putExtra(extraFlag, true);
         }
         intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
+        intent.putExtra(EXTRA_TRIGGERED_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
         if (opts != null) {
             mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
                     UserHandle.USER_CURRENT));
@@ -576,11 +444,9 @@
     @Override
     public void onAnimationStarted() {
         // Notify recents to start the enter animation
-        try {
-            Message msg = Message.obtain(null, MSG_START_ENTER_ANIMATION, 0, 0);
-            mService.send(msg);
-        } catch (RemoteException re) {
-            re.printStackTrace();
-        }
+        Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
+        intent.setPackage(mContext.getPackageName());
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mContext.sendBroadcast(intent);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 7a49a04..8a80b76 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -25,10 +25,12 @@
         public static final boolean Verbose = false;
 
         public static class App {
+            // Enables the simulated task affiliations
+            public static final boolean EnableSimulatedTaskGroups = false;
             // Enables the screenshot app->Recents transition
             public static final boolean EnableScreenshotAppTransition = false;
             // Enables the filtering of tasks according to their grouping
-            public static final boolean EnableTaskFiltering = true;
+            public static final boolean EnableTaskFiltering = false;
             // Enables clipping of tasks against each other
             public static final boolean EnableTaskStackClipping = true;
             // Enables tapping on the TaskBar to launch the task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 19a38c7..bf894a7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -35,6 +35,11 @@
 import android.view.ViewStub;
 import android.widget.Toast;
 import com.android.systemui.R;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.DebugTrigger;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+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.SpaceNode;
 import com.android.systemui.recents.model.TaskStack;
@@ -51,6 +56,11 @@
         RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
         FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
 
+    final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
+    final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
+    final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
+    final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
+
     RecentsView mRecentsView;
     SystemBarScrimViews mScrimViews;
     ViewStub mEmptyViewStub;
@@ -123,8 +133,8 @@
                 Console.log(Constants.Log.App.SystemUIHandshake,
                         "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
             }
-            if (action.equals(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY)) {
-                if (intent.getBooleanExtra(RecentsService.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
+            if (action.equals(ACTION_HIDE_RECENTS_ACTIVITY)) {
+                if (intent.getBooleanExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
                     // Dismiss recents, launching the focused task
                     dismissRecentsIfVisible();
                 } else {
@@ -138,13 +148,13 @@
                                 new ViewAnimation.TaskViewExitContext(exitTrigger));
                     }
                 }
-            } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
+            } else if (action.equals(ACTION_TOGGLE_RECENTS_ACTIVITY)) {
                 // Try and unfilter and filtered stacks
                 if (!mRecentsView.unfilterFilteredStacks()) {
                     // If there are no filtered stacks, dismiss recents and launch the first task
                     dismissRecentsIfVisible();
                 }
-            } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
+            } else if (action.equals(ACTION_START_ENTER_ANIMATION)) {
                 // Try and start the enter animation (or restart it on configuration changed)
                 ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
                 mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
@@ -438,9 +448,9 @@
 
         // Register the broadcast receiver to handle messages from our service
         IntentFilter filter = new IntentFilter();
-        filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
-        filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
-        filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION);
+        filter.addAction(ACTION_HIDE_RECENTS_ACTIVITY);
+        filter.addAction(ACTION_TOGGLE_RECENTS_ACTIVITY);
+        filter.addAction(ACTION_START_ENTER_ANIMATION);
         registerReceiver(mServiceBroadcastReceiver, filter);
 
         // Start listening for widget package changes if there is one bound
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 3754340..43d7a54 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -19,6 +19,7 @@
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 
 /** Our special app widget host for the Search widget */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 3041a3c..a2407eb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -28,6 +28,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import com.android.systemui.R;
+import com.android.systemui.recents.misc.Console;
 
 
 /** A static Recents configuration for the current context
@@ -75,7 +76,6 @@
     public int taskViewRemoveAnimTranslationXPx;
     public int taskViewTranslationZMinPx;
     public int taskViewTranslationZIncrementPx;
-    public int taskViewShadowOutlineBottomInsetPx;
     public int taskViewRoundedCornerRadiusPx;
     public int taskViewHighlightPx;
 
@@ -202,8 +202,6 @@
         taskViewTranslationZMinPx = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
         taskViewTranslationZIncrementPx =
                 res.getDimensionPixelSize(R.dimen.recents_task_view_z_increment);
-        taskViewShadowOutlineBottomInsetPx =
-                res.getDimensionPixelSize(R.dimen.recents_task_view_shadow_outline_bottom_inset);
 
         // Task bar colors
         taskBarViewDefaultBackgroundColor =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
deleted file mode 100644
index 49149a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
-import com.android.systemui.recents.views.TaskViewTransform;
-
-import java.lang.ref.WeakReference;
-
-
-/** The message handler to process Recents SysUI messages */
-class SystemUIMessageHandler extends Handler {
-    WeakReference<Context> mContext;
-
-    SystemUIMessageHandler(Context context) {
-        // Keep a weak ref to the context instead of a strong ref
-        mContext = new WeakReference<Context>(context);
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake,
-                    "[RecentsService|handleMessage]", msg);
-        }
-
-        Context context = mContext.get();
-        if (context == null) return;
-
-        if (msg.what == AlternateRecentsComponent.MSG_UPDATE_FOR_CONFIGURATION) {
-            RecentsTaskLoader.initialize(context);
-            RecentsConfiguration config = RecentsConfiguration.reinitialize(context);
-            config.updateOnConfigurationChange();
-
-            try {
-                Bundle data = msg.getData();
-                Rect windowRect = data.getParcelable(AlternateRecentsComponent.KEY_WINDOW_RECT);
-                Rect systemInsets = data.getParcelable(AlternateRecentsComponent.KEY_SYSTEM_INSETS);
-
-                // NOTE: None of the rects computed below need to be offset for the status bar,
-                // since that is done when we compute the animation itself in the Recents component
-
-                // Create a dummy task stack & compute the rect for the thumbnail to animate to
-                TaskStack stack = new TaskStack();
-                TaskStackView tsv = new TaskStackView(context, stack);
-                TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
-                Bundle replyData = new Bundle();
-                TaskViewTransform transform;
-
-                // Get the task stack and search bar bounds
-                Rect taskStackBounds = new Rect();
-                config.getTaskStackBounds(windowRect.width(), windowRect.height(), taskStackBounds);
-
-                // Calculate the target task rect for when there is one task.
-
-                // NOTE: Since the nav bar height is already accounted for in the windowRect, don't
-                // pass in a left or bottom inset
-                stack.addTask(new Task());
-                tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
-                        systemInsets.top - systemInsets.bottom, 0, 0);
-                tsv.setStackScrollToInitialState();
-                transform = algo.getStackTransform(0, tsv.getStackScroll());
-                transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
-                replyData.putParcelable(AlternateRecentsComponent.KEY_SINGLE_TASK_STACK_RECT,
-                        new Rect(transform.rect));
-
-                // Also calculate the target task rect when there are two tasks.
-                stack.addTask(new Task());
-                tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
-                        systemInsets.top - systemInsets.bottom, 0, 0);
-                tsv.setStackScrollToInitialState();
-                transform = algo.getStackTransform(1, tsv.getStackScroll());
-                transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
-                replyData.putParcelable(AlternateRecentsComponent.KEY_TWO_TASK_STACK_RECT,
-                        new Rect(transform.rect));
-
-                // Also calculate the target task rect when there are two tasks.
-                stack.addTask(new Task());
-                tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
-                        systemInsets.top - systemInsets.bottom, 0, 0);
-                tsv.setStackScrollToInitialState();
-                transform = algo.getStackTransform(2, tsv.getStackScroll());
-                transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
-                replyData.putParcelable(AlternateRecentsComponent.KEY_MULTIPLE_TASK_STACK_RECT,
-                        new Rect(transform.rect));
-
-                data.putParcelable(AlternateRecentsComponent.KEY_CONFIGURATION_DATA, replyData);
-                Message reply = Message.obtain(null,
-                        AlternateRecentsComponent.MSG_UPDATE_FOR_CONFIGURATION, 0, 0);
-                reply.setData(data);
-                msg.replyTo.send(reply);
-            } catch (RemoteException re) {
-                re.printStackTrace();
-            }
-        } else if (msg.what == AlternateRecentsComponent.MSG_HIDE_RECENTS) {
-            // Send a broadcast to hide recents
-            Intent intent = new Intent(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
-            intent.setPackage(context.getPackageName());
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            if (msg.arg1 != 0) {
-                intent.putExtra(RecentsService.EXTRA_TRIGGERED_FROM_ALT_TAB, true);
-            }
-            context.sendBroadcast(intent);
-        } else if (msg.what == AlternateRecentsComponent.MSG_TOGGLE_RECENTS) {
-            // Send a broadcast to toggle recents
-            Intent intent = new Intent(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
-            intent.setPackage(context.getPackageName());
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            context.sendBroadcast(intent);
-
-            // Time this path
-            if (Console.Enabled) {
-                Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                        Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
-                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                        Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
-            }
-        } else if (msg.what == AlternateRecentsComponent.MSG_START_ENTER_ANIMATION) {
-            // Send a broadcast to start the enter animation
-            Intent intent = new Intent(RecentsService.ACTION_START_ENTER_ANIMATION);
-            intent.setPackage(context.getPackageName());
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            context.sendBroadcast(intent);
-        }
-    }
-}
-
-/* Service */
-public class RecentsService extends Service {
-    final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
-    final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
-    final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
-    final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
-
-    Messenger mSystemUIMessenger = new Messenger(new SystemUIMessageHandler(this));
-
-    @Override
-    public void onCreate() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsService|onCreate]");
-        }
-        super.onCreate();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsService|onBind]");
-        }
-        return mSystemUIMessenger.getBinder();
-    }
-
-    @Override
-    public boolean onUnbind(Intent intent) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsService|onUnbind]");
-        }
-        return super.onUnbind(intent);
-    }
-
-    @Override
-    public void onRebind(Intent intent) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsService|onRebind]");
-        }
-        super.onRebind(intent);
-    }
-
-    @Override
-    public void onDestroy() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsService|onDestroy]");
-        }
-        super.onDestroy();
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        if (loader != null) {
-            loader.onTrimMemory(level);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Console.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/recents/Console.java
rename to packages/SystemUI/src/com/android/systemui/recents/misc/Console.java
index 0cb74b9..28ac9d3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Console.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents;
+package com.android.systemui.recents.misc;
 
 
 import android.content.ComponentCallbacks2;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java
rename to packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java
index d90e2be..d000985 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents;
+package com.android.systemui.recents.misc;
 
 import android.os.Handler;
 import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java
rename to packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
index f0b2cb6..4456066 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents;
+package com.android.systemui.recents.misc;
 
 import android.os.Handler;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java b/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java
new file mode 100644
index 0000000..ec3c39c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+/**
+ * Used to generate successive incremented names.
+ */
+public class NamedCounter {
+
+    int mCount;
+    String mPrefix = "";
+    String mSuffix = "";
+
+    public NamedCounter(String prefix, String suffix) {
+        mPrefix = prefix;
+        mSuffix = suffix;
+    }
+
+    /** Returns the next name. */
+    public String nextName() {
+        String name = mPrefix + mCount + mSuffix;
+        mCount++;
+        return name;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java
rename to packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
index d525546..31825af 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents;
+package com.android.systemui.recents.misc;
 
 import android.content.Context;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
rename to packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 3765d1c..05c0f58 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents;
+package com.android.systemui.recents.misc;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -30,14 +30,17 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -47,7 +50,9 @@
 import android.view.DisplayInfo;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import com.android.systemui.recents.Constants;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -128,6 +133,7 @@
                 rti.baseIntent = new Intent();
                 rti.baseIntent.setComponent(cn);
                 rti.description = description;
+                rti.firstActiveTime = rti.lastActiveTime = i;
                 if (i % 2 == 0) {
                     rti.taskDescription = new ActivityManager.TaskDescription(description,
                         Bitmap.createBitmap(mDummyIcon),
@@ -208,7 +214,7 @@
             return thumbnail;
         }
 
-        Bitmap thumbnail = Utilities.getThumbnail(mAm, taskId);
+        Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
         if (thumbnail != null) {
             // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
             // left pixel, then assume the whole thumbnail is transparent. Generally, proper
@@ -224,6 +230,25 @@
         return thumbnail;
     }
 
+    /**
+     * Returns a task thumbnail from the activity manager
+     */
+    public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
+        ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+        Bitmap thumbnail = taskThumbnail.mainThumbnail;
+        ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
+        if (thumbnail == null && descriptor != null) {
+            thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor());
+        }
+        if (descriptor != null) {
+            try {
+                descriptor.close();
+            } catch (IOException e) {
+            }
+        }
+        return thumbnail;
+    }
+
     /** Moves a task to the front with the specified activity options */
     public void moveTaskToFront(int taskId, ActivityOptions opts) {
         if (mAm == null) return;
@@ -371,6 +396,17 @@
     }
 
     /**
+     * Returns the window rect.
+     */
+    public Rect getWindowRect() {
+        Rect windowRect = new Rect();
+        if (mWm == null) return windowRect;
+
+        mWm.getDefaultDisplay().getRectSize(windowRect);
+        return windowRect;
+    }
+
+    /**
      * Takes a screenshot of the current surface.
      */
     public Bitmap takeScreenshot() {
@@ -378,4 +414,11 @@
         mDisplay.getDisplayInfo(di);
         return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight());
     }
+
+    /**
+     * Takes a screenshot of the current app.
+     */
+    public Bitmap takeAppScreenshot() {
+        return takeScreenshot();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/recents/Utilities.java
rename to packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 6a7cfcc..bda195b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents;
+package com.android.systemui.recents.misc;
 
 import android.app.ActivityManager;
 import android.graphics.Bitmap;
@@ -23,6 +23,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.ParcelFileDescriptor;
+import com.android.systemui.recents.RecentsConfiguration;
 
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
@@ -95,21 +96,4 @@
             throws IllegalAccessException, InvocationTargetException {
         sPropertyMethod.invoke(null, property, value);
     }
-
-    /** Retrieves a task thumbnail from the activity manager */
-    public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
-        ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
-        Bitmap thumbnail = taskThumbnail.mainThumbnail;
-        final ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
-        if (thumbnail == null && descriptor != null) {
-            thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor());
-        }
-        if (descriptor != null) {
-            try {
-                descriptor.close();
-            } catch (IOException e) {
-            }
-        }
-        return thumbnail;
-    }
 }
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 cbe39e3..2d50659 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -21,7 +21,7 @@
 import android.content.Context;
 import android.os.Looper;
 import com.android.internal.content.PackageMonitor;
-import com.android.systemui.recents.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import java.util.HashSet;
 import java.util.List;
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 9d8d746..29f1440 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -28,10 +28,10 @@
 import android.os.HandlerThread;
 import android.os.UserHandle;
 import android.util.Pair;
-import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.SystemServicesProxy;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -368,10 +368,9 @@
         return mSystemServicesProxy;
     }
 
-    private List<ActivityManager.RecentTaskInfo> getRecentTasks() {
+    private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp) {
         long t1 = System.currentTimeMillis();
 
-        SystemServicesProxy ssp = mSystemServicesProxy;
         List<ActivityManager.RecentTaskInfo> tasks =
                 ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier());
         Collections.reverse(tasks);
@@ -402,7 +401,7 @@
 
         // Get the recent tasks
         SystemServicesProxy ssp = mSystemServicesProxy;
-        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks();
+        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
 
         // Add each task to the task stack
         t1 = System.currentTimeMillis();
@@ -430,7 +429,7 @@
 
             // Create a new task
             Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
-                    activityIcon, activityColor, t.userId, t.lastActiveTime);
+                    activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime);
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
@@ -495,6 +494,9 @@
                     "" + (System.currentTimeMillis() - t1) + "ms");
         }
 
+        // Simulate the groupings that we describe
+        stack.createSimulatedAffiliatedGroupings();
+
         // Start the task loader
         mLoader.start(context);
 
@@ -509,6 +511,24 @@
         return root;
     }
 
+    /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */
+    public static TaskStack getShallowTaskStack(SystemServicesProxy ssp) {
+        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
+        TaskStack stack = new TaskStack();
+
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            ActivityManager.RecentTaskInfo t = tasks.get(i);
+            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
+            if (info == null) continue;
+
+            stack.addTask(new Task(t.persistentId, true, t.baseIntent, null, null, 0, 0,
+                    t.firstActiveTime, t.lastActiveTime));
+        }
+        stack.createSimulatedAffiliatedGroupings();
+        return stack;
+    }
+
     /** Acquires the task resource data directly from the pool. */
     public void loadTaskData(Task t) {
         Drawable applicationIcon = mApplicationIconCache.get(t.key);
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 abfb221..002395f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -19,7 +19,7 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.misc.Utilities;
 
 
 /**
@@ -39,12 +39,14 @@
         public final int id;
         public final Intent baseIntent;
         public final int userId;
+        public long firstActiveTime;
         public long lastActiveTime;
 
-        public TaskKey(int id, Intent intent, int userId, long lastActiveTime) {
+        public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) {
             this.id = id;
             this.baseIntent = intent;
             this.userId = userId;
+            this.firstActiveTime = firstActiveTime;
             this.lastActiveTime = lastActiveTime;
         }
 
@@ -76,6 +78,7 @@
     }
 
     public TaskKey key;
+    public TaskGrouping group;
     public Drawable applicationIcon;
     public Drawable activityIcon;
     public String activityLabel;
@@ -92,8 +95,9 @@
     }
 
     public Task(int id, boolean isActive, Intent intent, String activityTitle,
-                Drawable activityIcon, int colorPrimary, int userId, long lastActiveTime) {
-        this.key = new TaskKey(id, intent, userId, lastActiveTime);
+                Drawable activityIcon, int colorPrimary, int userId, long firstActiveTime,
+                long lastActiveTime) {
+        this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
         this.activityLabel = activityTitle;
         this.activityIcon = activityIcon;
         this.colorPrimary = colorPrimary;
@@ -107,6 +111,14 @@
         mCb = cb;
     }
 
+    /** Set the grouping */
+    public void setGroup(TaskGrouping group) {
+        if (group != null && this.group != null) {
+            throw new RuntimeException("This task is already assigned to a group.");
+        }
+        this.group = group;
+    }
+
     /** Notifies the callback listeners that this task has been loaded */
     public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
         this.applicationIcon = applicationIcon;
@@ -134,6 +146,11 @@
 
     @Override
     public String toString() {
-        return "Task: " + key.baseIntent.getComponent().getPackageName() + " [" + super.toString() + "]";
+        String groupAffiliation = "no group";
+        if (group != null) {
+            groupAffiliation = group.affiliation;
+        }
+        return "Task (" + groupAffiliation + "): " + key.baseIntent.getComponent().getPackageName() +
+                " [" + super.toString() + "]";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
new file mode 100644
index 0000000..3a18bce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
@@ -0,0 +1,53 @@
+package com.android.systemui.recents.model;
+
+import java.util.ArrayList;
+
+/** Represents a grouping of tasks witihin a stack. */
+public class TaskGrouping {
+
+    String affiliation;
+    long latestActiveTimeInGroup;
+
+    ArrayList<Task.TaskKey> mTasks = new ArrayList<Task.TaskKey>();
+
+    /** Creates a group with a specified affiliation. */
+    public TaskGrouping(String affiliation) {
+        this.affiliation = affiliation;
+    }
+
+    /** Adds a new task to this group. */
+    void addTask(Task t) {
+        mTasks.add(t.key);
+        if (t.key.lastActiveTime > latestActiveTimeInGroup) {
+            latestActiveTimeInGroup = t.key.lastActiveTime;
+        }
+        t.setGroup(this);
+    }
+
+    /** Removes a task from this group. */
+    void removeTask(Task t) {
+        mTasks.remove(t.key);
+        latestActiveTimeInGroup = 0;
+        int taskCount = mTasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            long lastActiveTime = mTasks.get(i).lastActiveTime;
+            if (lastActiveTime > latestActiveTimeInGroup) {
+                latestActiveTimeInGroup = lastActiveTime;
+            }
+        }
+        t.setGroup(null);
+    }
+
+    /** Gets the front task */
+    public boolean isFrontMostTask(Task t) {
+        return t.key.equals(mTasks.get(mTasks.size() - 1));
+    }
+
+    /** Finds the index of a given task in a group. */
+    public int indexOf(Task t) {
+        return mTasks.indexOf(t.key);
+    }
+
+    /** Returns the number of tasks in this group. */
+    public int getTaskCount() { return mTasks.size(); }
+}
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 24e01bd..ffd2857 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -16,9 +16,13 @@
 
 package com.android.systemui.recents.model;
 
-import android.content.Context;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.misc.NamedCounter;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 
 
@@ -36,6 +40,7 @@
 class FilteredTaskList {
     ArrayList<Task> mTasks = new ArrayList<Task>();
     ArrayList<Task> mFilteredTasks = new ArrayList<Task>();
+    HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<Task.TaskKey, Integer>();
     TaskFilter mFilter;
 
     /** Sets the task filter, saving the current touch state */
@@ -83,7 +88,7 @@
 
     /** Returns the index of this task in the list of filtered tasks */
     int indexOf(Task t) {
-        return mFilteredTasks.indexOf(t);
+        return mTaskIndices.get(t.key);
     }
 
     /** Returns the size of the list of filtered tasks */
@@ -93,7 +98,7 @@
 
     /** Returns whether the filtered list contains this task */
     boolean contains(Task t) {
-        return mFilteredTasks.contains(t);
+        return mTaskIndices.containsKey(t.key);
     }
 
     /** Updates the list of filtered tasks whenever the base task list changes */
@@ -110,6 +115,17 @@
         } else {
             mFilteredTasks.addAll(mTasks);
         }
+        updateFilteredTaskIndices();
+    }
+
+    /** Updates the mapping of tasks to indices. */
+    private void updateFilteredTaskIndices() {
+        mTaskIndices.clear();
+        int taskCount = mFilteredTasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task t = mFilteredTasks.get(i);
+            mTaskIndices.put(t.key, i);
+        }
     }
 
     /** Returns whether this task list is filtered */
@@ -127,7 +143,8 @@
  * The task stack contains a list of multiple tasks.
  */
 public class TaskStack {
-    /* Task stack callbacks */
+
+    /** Task stack callbacks */
     public interface TaskStackCallbacks {
         /* Notifies when a task has been added to the stack */
         public void onStackTaskAdded(TaskStack stack, Task t);
@@ -139,11 +156,25 @@
         public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
     }
 
+    /** A pair of indices representing the group and task positions in the stack and group. */
+    public static class GroupTaskIndex {
+        public int groupIndex; // Index in the stack
+        public int taskIndex;  // Index in the group
+
+        public GroupTaskIndex() {}
+
+        public GroupTaskIndex(int gi, int ti) {
+            groupIndex = gi;
+            taskIndex = ti;
+        }
+    }
+
     FilteredTaskList mTaskList = new FilteredTaskList();
     TaskStackCallbacks mCb;
 
-    public TaskStack() {
-    }
+    ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
+    HashMap<String, TaskGrouping> mAffinitiesGroups = new HashMap<String, TaskGrouping>();
+    HashMap<TaskGrouping, Integer> mGroupsIndices = new HashMap<TaskGrouping, Integer>();
 
     /** Sets the callbacks for this task stack */
     public void setCallbacks(TaskStackCallbacks cb) {
@@ -161,8 +192,16 @@
     /** Removes a task */
     public void removeTask(Task t) {
         if (mTaskList.contains(t)) {
+            // Remove the task from the list
             mTaskList.remove(t);
+            // Remove it from the group as well, and if it is empty, remove the group
+            TaskGrouping group = t.group;
+            group.removeTask(t);
+            if (group.getTaskCount() == 0) {
+                removeGroup(group);
+            }
             if (mCb != null) {
+                // Notify that a task has been removed
                 mCb.onStackTaskRemoved(this, t);
             }
         }
@@ -170,10 +209,20 @@
 
     /** Sets a few tasks in one go */
     public void setTasks(List<Task> tasks) {
-        int taskCount = mTaskList.getTasks().size();
+        ArrayList<Task> taskList = mTaskList.getTasks();
+        int taskCount = taskList.size();
         for (int i = 0; i < taskCount; i++) {
-            Task t = mTaskList.getTasks().get(i);
+            Task t = taskList.get(i);
+            // Remove the task from the list
+            mTaskList.remove(t);
+            // Remove it from the group as well, and if it is empty, remove the group
+            TaskGrouping group = t.group;
+            group.removeTask(t);
+            if (group.getTaskCount() == 0) {
+                removeGroup(group);
+            }
             if (mCb != null) {
+                // Notify that a task has been removed
                 mCb.onStackTaskRemoved(this, t);
             }
         }
@@ -185,6 +234,11 @@
         }
     }
 
+    /** Gets the front task */
+    public Task getFrontMostTask() {
+        return mTaskList.getTasks().get(mTaskList.size() - 1);
+    }
+
     /** Gets the tasks */
     public ArrayList<Task> getTasks() {
         return mTaskList.getTasks();
@@ -200,10 +254,7 @@
         return mTaskList.indexOf(t);
     }
 
-    /** Tests whether a task is in this current task stack */
-    public boolean containsTask(Task t) {
-        return mTaskList.contains(t);
-    }
+    /******** Filtering ********/
 
     /** Filters the stack into tasks similar to the one specified */
     public void filterTasks(final Task t) {
@@ -238,6 +289,135 @@
         return mTaskList.hasFilter();
     }
 
+    /******** Grouping ********/
+
+    /** Adds a group to the set */
+    public void addGroup(TaskGrouping group) {
+        mGroups.add(group);
+        mAffinitiesGroups.put(group.affiliation, group);
+        updateTaskGroupingIndices();
+    }
+
+    public void removeGroup(TaskGrouping group) {
+        // XXX: Ensure that there are no more tasks in this group
+        mGroups.remove(group);
+        mAffinitiesGroups.remove(group.affiliation);
+        mGroupsIndices.remove(group);
+        updateTaskGroupingIndices();
+    }
+
+    /** Adds a mapping from a task to a group. */
+    public void addTaskToGroup(TaskGrouping group, Task task) {
+        if (!mAffinitiesGroups.containsKey(group.affiliation)) {
+            throw new RuntimeException("Unexpected group");
+        }
+        group.addTask(task);
+    }
+
+    /** Returns the group with the specified affiliation. */
+    public TaskGrouping getGroupWithAffiliation(String affiliation) {
+        return mAffinitiesGroups.get(affiliation);
+    }
+
+    /** Returns the number of groups. */
+    public int getGroupingCount() {
+        return mGroups.size();
+    }
+
+    /** Returns the group and task indices for a given task. */
+    public void getGroupIndexForTask(Task task, GroupTaskIndex indices) {
+        indices.groupIndex = mGroupsIndices.get(task.group);
+        indices.taskIndex = task.group.indexOf(task);
+    }
+
+    /**
+     * Temporary: This method will simulate affiliation groups by
+     */
+    public void createSimulatedAffiliatedGroupings() {
+        if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
+            HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
+            // Sort all tasks by increasing firstActiveTime of the task
+            ArrayList<Task> tasks = mTaskList.getTasks();
+            Collections.sort(tasks, new Comparator<Task>() {
+                @Override
+                public int compare(Task task, Task task2) {
+                    return (int) (task.key.firstActiveTime - task2.key.firstActiveTime);
+                }
+            });
+            // Create groups when sequential packages are the same
+            NamedCounter counter = new NamedCounter("task-group", "");
+            int taskCount = tasks.size();
+            String prevPackage = "";
+            String prevAffiliation = "";
+            for (int i = 0; i < taskCount; i++) {
+                Task t = tasks.get(i);
+                String packageName = t.key.baseIntent.getComponent().getPackageName();
+                TaskGrouping group;
+                if (packageName.equals(prevPackage)) {
+                    group = getGroupWithAffiliation(prevAffiliation);
+                } else {
+                    String affiliation = counter.nextName();
+                    group = new TaskGrouping(affiliation);
+                    addGroup(group);
+                    prevAffiliation = affiliation;
+                    prevPackage = packageName;
+                }
+                group.addTask(t);
+                taskMap.put(t.key, t);
+            }
+            // Sort groups by increasing latestActiveTime of the group
+            Collections.sort(mGroups, new Comparator<TaskGrouping>() {
+                @Override
+                public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
+                    return (int) (taskGrouping.latestActiveTimeInGroup -
+                            taskGrouping2.latestActiveTimeInGroup);
+                }
+            });
+            updateTaskGroupingIndices();
+            // Sort group tasks by increasing firstActiveTime of the task, and also build a new list of
+            // tasks
+            int taskIndex = 0;
+            int groupCount = mGroups.size();
+            for (int i = 0; i < groupCount; i++) {
+                TaskGrouping group = mGroups.get(i);
+                Collections.sort(group.mTasks, new Comparator<Task.TaskKey>() {
+                    @Override
+                    public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
+                        return (int) (taskKey.firstActiveTime - taskKey2.firstActiveTime);
+                    }
+                });
+                ArrayList<Task.TaskKey> groupTasks = group.mTasks;
+                int groupTaskCount = groupTasks.size();
+                for (int j = 0; j < groupTaskCount; j++) {
+                    tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
+                    taskIndex++;
+                }
+            }
+            mTaskList.set(tasks);
+        } else {
+            // Create a group per task
+            NamedCounter counter = new NamedCounter("task-group", "");
+            ArrayList<Task> tasks = mTaskList.getTasks();
+            int taskCount = tasks.size();
+            for (int i = 0; i < taskCount; i++) {
+                Task t = tasks.get(i);
+                TaskGrouping group = new TaskGrouping(counter.nextName());
+                addGroup(group);
+                group.addTask(t);
+            }
+        }
+    }
+
+    /** Updates the mapping of tasks to indices. */
+    private void updateTaskGroupingIndices() {
+        mGroupsIndices.clear();
+        int groupsCount = mGroups.size();
+        for (int i = 0; i < groupsCount; i++) {
+            TaskGrouping g = mGroups.get(i);
+            mGroupsIndices.put(g, i);
+        }
+    }
+
     @Override
     public String toString() {
         String str = "Tasks:\n";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
index 6568b1a..49a7ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -31,7 +30,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import com.android.systemui.R;
-import com.android.systemui.recents.Console;
+import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 
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 f203d3e..87d45ba 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -34,7 +34,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
-import com.android.systemui.recents.Console;
+import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.RecentsPackageMonitor;
@@ -438,23 +438,25 @@
         }
 
         // Upfront the processing of the thumbnail
-        TaskViewTransform transform;
+        TaskViewTransform transform = new TaskViewTransform();
         View sourceView = tv;
         int offsetX = 0;
         int offsetY = 0;
         int stackScroll = stackView.getStackScroll();
+        TaskStack.GroupTaskIndex groupTaskIndex = new TaskStack.GroupTaskIndex();
+        stack.getGroupIndexForTask(task, groupTaskIndex);
         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)
             sourceView = stackView;
-            transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task),
-                    stackScroll);
+            transform = stackView.getStackAlgorithm().getStackTransform(groupTaskIndex.groupIndex,
+                    groupTaskIndex.taskIndex, stackScroll, transform);
             offsetX = transform.rect.left;
             offsetY = Math.min(transform.rect.top, mConfig.displayRect.height());
         } else {
-            transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task),
-                    stackScroll);
+            transform = stackView.getStackAlgorithm().getStackTransform(groupTaskIndex.groupIndex,
+                    groupTaskIndex.taskIndex, stackScroll, transform);
         }
 
         // Compute the thumbnail to scale up from
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 cae6bd7..8409227a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -28,7 +28,7 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.animation.LinearInterpolator;
-import com.android.systemui.recents.Console;
+import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index 9c60603..4ed3b59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -34,7 +34,7 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.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 dd47fddf..4a1faee 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -31,18 +31,19 @@
 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.DozeTrigger;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.ReferenceCountedTrigger;
-import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.DozeTrigger;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Set;
 
 
@@ -180,7 +181,18 @@
         mStackViewsDirty = true;
     }
 
-    /** Finds the child view given a specific task */
+    /** Returns a mapping of child view to Task. */
+    HashMap<Task, TaskView> getTaskChildViewMap() {
+        HashMap<Task, TaskView> taskViewMap = new HashMap<Task, TaskView>();
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            taskViewMap.put(tv.getTask(), tv);
+        }
+        return taskViewMap;
+    }
+
+    /** Finds the child view given a specific task. */
     TaskView getChildViewForTask(Task t) {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -225,9 +237,11 @@
         }
 
         // Update the stack transforms
+        TaskStack.GroupTaskIndex groupTaskIndex = new TaskStack.GroupTaskIndex();
         for (int i = taskCount - 1; i >= 0; i--) {
-            TaskViewTransform transform = mStackAlgorithm.getStackTransform(i, stackScroll,
-                    taskTransforms.get(i));
+            mStack.getGroupIndexForTask(tasks.get(i), groupTaskIndex);
+            TaskViewTransform transform = mStackAlgorithm.getStackTransform(groupTaskIndex.groupIndex,
+                    groupTaskIndex.taskIndex, stackScroll, taskTransforms.get(i));
             if (transform.visible) {
                 if (frontMostVisibleIndex < 0) {
                     frontMostVisibleIndex = i;
@@ -253,6 +267,11 @@
         if (visibleRangeOut != null) {
             visibleRangeOut[0] = frontMostVisibleIndex;
             visibleRangeOut[1] = backMostVisibleIndex;
+            if (Console.Enabled) {
+                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
+                        "[TaskStackView|updateStackTransforms]",
+                        "Back: " + backMostVisibleIndex + " Front: " + frontMostVisibleIndex);
+            }
         }
     }
 
@@ -278,58 +297,57 @@
                     "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
         }
         if (mStackViewsDirty) {
-            // 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
-
             // Get all the task transforms
-            int[] visibleRange = mTmpVisibleRange;
-            int stackScroll = getStackScroll();
             ArrayList<Task> tasks = mStack.getTasks();
+            int stackScroll = getStackScroll();
+            int[] visibleRange = mTmpVisibleRange;
             updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
+            TaskViewTransform tmpTransform = new TaskViewTransform();
+            TaskStack.GroupTaskIndex gti = new TaskStack.GroupTaskIndex();
 
-            // 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 = mCurrentTaskTransforms.get(i);
-                TaskView tv = getChildViewForTask(task);
-
-                if (transform.visible) {
-                    if (tv == null) {
-                        tv = mViewPool.pickUpViewFromPool(task, task);
-                        // 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) {
-                            int fromIndex = (transform.t < 0) ?
-                                    Math.max(0, (visibleRange[1] - 1)) :
-                                    Math.min(taskCount - 1, (visibleRange[0] + 1));
-                            tv.updateViewPropertiesToTaskTransform(
-                                    mStackAlgorithm.getStackTransform(fromIndex, stackScroll), 0);
-                        }
-                    }
-                } else {
-                    if (tv != null) {
-                        mViewPool.returnViewToPool(tv);
-                    }
-                }
-            }
-
-            // Update all the remaining view children
-            // NOTE: We have to iterate in reverse where because we are removing views directly
+            // Return all the invisible children to the pool
+            HashMap<Task, TaskView> taskChildViewMap = getTaskChildViewMap();
             int childCount = getChildCount();
             for (int i = childCount - 1; i >= 0; i--) {
                 TaskView tv = (TaskView) getChildAt(i);
                 Task task = tv.getTask();
                 int taskIndex = mStack.indexOfTask(task);
-                if (taskIndex < 0 || !mCurrentTaskTransforms.get(taskIndex).visible) {
+                if (taskIndex < visibleRange[1] || taskIndex > visibleRange[0]) {
+                    taskChildViewMap.remove(task);
                     mViewPool.returnViewToPool(tv);
-                } else {
-                    tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
-                            mStackViewsAnimationDuration);
                 }
             }
 
+            // Pick up all the newly visible children and update all the existing children
+            boolean isValidVisibleRange = visibleRange[0] != -1 && visibleRange[1] != -1;
+            for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
+                Task task = tasks.get(i);
+                TaskViewTransform transform = mCurrentTaskTransforms.get(i);
+                TaskView tv = taskChildViewMap.get(task);
+                int taskIndex = mStack.indexOfTask(task);
+
+                if (tv == null) {
+                    tv = mViewPool.pickUpViewFromPool(task, task);
+                    if (mStackViewsAnimationDuration > 0) {
+                        // For items in the list, put them in start animating them from the
+                        // approriate ends of the list where they are expected to appear
+                        Task fromTask = (transform.t < 0) ?
+                                tasks.get(visibleRange[1]) :
+                                tasks.get(visibleRange[0]);
+                        mStack.getGroupIndexForTask(fromTask, gti);
+                        tmpTransform = mStackAlgorithm.getStackTransform(
+                                (transform.t < 0) ? gti.groupIndex - 1 : gti.groupIndex + 1,
+                                (transform.t < 0) ? gti.taskIndex - 1 : gti.taskIndex + 1,
+                                stackScroll, tmpTransform);
+                        tv.updateViewPropertiesToTaskTransform(tmpTransform, 0);
+                    }
+                }
+
+                // Update and animate the task into place
+                tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
+                        mStackViewsAnimationDuration);
+            }
+
             if (Console.Enabled) {
                 Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
                         "  [TaskStackView|viewChildren]", "" + getChildCount());
@@ -357,7 +375,7 @@
     }
     /** Computes the initial stack scroll for the stack. */
     int getInitialStackScroll() {
-        if (mStack.getTaskCount() > 2) {
+        if (mStack.getGroupingCount() > 2) {
             return mMaxScroll - mStackAlgorithm.mTaskRect.height() / 2;
         }
         return mMaxScroll;
@@ -477,7 +495,7 @@
     /** Updates the min and max virtual scroll bounds */
     void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
         // Compute the min and max scroll values
-        mStackAlgorithm.computeMinMaxScroll(mStack.getTaskCount());
+        mStackAlgorithm.computeMinMaxScroll(mStack.getGroupingCount());
         mMinScroll = mStackAlgorithm.mMinScroll;
         mMaxScroll = mStackAlgorithm.mMaxScroll;
 
@@ -794,30 +812,37 @@
             return;
         }
 
-        // Animate all the task views into view
-        TaskViewTransform transform = mStackAlgorithm.getStackTransform(mStack.getTaskCount() - 1,
-                getInitialStackScroll());
-        ctx.taskRect = transform.rect;
-        ctx.stackRectSansPeek = mStackAlgorithm.mStackRectSansPeek;
-        int childCount = getChildCount();
-        for (int i = childCount - 1; i >= 0; i--) {
-            TaskView tv = (TaskView) getChildAt(i);
-            ctx.stackViewIndex = i;
-            ctx.stackViewCount = childCount;
-            ctx.isFrontMost = (i == (getChildCount() - 1));
-            ctx.transform = mStackAlgorithm.getStackTransform(
-                    mStack.indexOfTask(tv.getTask()), getStackScroll());
-            tv.startEnterRecentsAnimation(ctx);
-        }
-
-        // Add a runnable to the post animation ref counter to clear all the views
-        ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-            @Override
-            public void run() {
-                // Start dozing
-                mUIDozeTrigger.startDozing();
+        if (mStack.getTaskCount() > 0) {
+            // Animate all the task views into view
+            TaskViewTransform transform = new TaskViewTransform();
+            TaskStack.GroupTaskIndex groupTaskIndex = new TaskStack.GroupTaskIndex();
+            mStack.getGroupIndexForTask(mStack.getFrontMostTask(), groupTaskIndex);
+            mStackAlgorithm.getStackTransform(groupTaskIndex.groupIndex, groupTaskIndex.taskIndex,
+                    getInitialStackScroll(), transform);
+            ctx.taskRect = transform.rect;
+            ctx.stackRectSansPeek = mStackAlgorithm.mStackRectSansPeek;
+            int childCount = getChildCount();
+            for (int i = childCount - 1; i >= 0; i--) {
+                TaskView tv = (TaskView) getChildAt(i);
+                ctx.stackViewIndex = i;
+                ctx.stackViewCount = childCount;
+                ctx.isFrontMost = (i == (getChildCount() - 1));
+                ctx.transform = new TaskViewTransform();
+                mStack.getGroupIndexForTask(tv.getTask(), groupTaskIndex);
+                mStackAlgorithm.getStackTransform(groupTaskIndex.groupIndex, groupTaskIndex.taskIndex,
+                        getStackScroll(), ctx.transform);
+                tv.startEnterRecentsAnimation(ctx);
             }
-        });
+
+            // Add a runnable to the post animation ref counter to clear all the views
+            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+                @Override
+                public void run() {
+                    // Start dozing
+                    mUIDozeTrigger.startDozing();
+                }
+            });
+        }
     }
 
     /** Requests this task stacks to start it's exit-recents animation. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index daa18bc..ab47757 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -19,7 +19,7 @@
 import android.graphics.Rect;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.misc.Utilities;
 
 /* The layout logic for a TaskStackView */
 public class TaskStackViewLayoutAlgorithm {
@@ -94,17 +94,11 @@
         }
     }
 
-    /** Update/get the transform (creates a new TaskViewTransform) */
-    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
-        TaskViewTransform transform = new TaskViewTransform();
-        return getStackTransform(indexInStack, stackScroll, transform);
-    }
-
     /** Update/get the transform */
-    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll,
-                                               TaskViewTransform transformOut) {
+    public TaskViewTransform getStackTransform(int groupIndexInStack, int taskIndexInGroup,
+                                               int stackScroll, TaskViewTransform transformOut) {
         // Return early if we have an invalid index
-        if (indexInStack < 0) {
+        if (groupIndexInStack < 0) {
             transformOut.reset();
             return transformOut;
         }
@@ -113,7 +107,7 @@
         int numPeekCards = StackPeekNumCards;
         float overlapHeight = StackOverlapPct * mTaskRect.height();
         float peekHeight = StackPeekHeightPct * mStackRect.height();
-        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
+        float t = ((groupIndexInStack * overlapHeight) - stackScroll) / overlapHeight;
         float boundedT = Math.max(t, -(numPeekCards + 1));
 
         // Set the scale relative to its position
@@ -133,6 +127,7 @@
         } else {
             transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
         }
+        transformOut.translationY += 100 * taskIndexInGroup;
 
         // Set the z translation
         int minZ = mConfig.taskViewTranslationZMinPx;
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 304d45c..e186e2e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -22,7 +22,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewParent;
-import com.android.systemui.recents.Console;
+import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 
 /* Handles touch events for a TaskStackView. */
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 6b06945..0d0fccc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -25,18 +25,14 @@
 import android.graphics.Canvas;
 import android.graphics.Outline;
 import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
-import com.android.systemui.recents.Console;
+import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
@@ -61,6 +57,7 @@
     Task mTask;
     boolean mTaskDataLoaded;
     boolean mIsFocused;
+    boolean mIsStub;
     boolean mClipViewInStack;
     Rect mTmpRect = new Rect();
     Paint mLayerPaint = new Paint();
@@ -133,8 +130,8 @@
 
         // Update the outline
         Outline o = new Outline();
-        o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() -
-                mConfig.taskViewShadowOutlineBottomInsetPx, mConfig.taskViewRoundedCornerRadiusPx);
+        o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+                mConfig.taskViewRoundedCornerRadiusPx);
         setOutline(o);
     }
 
@@ -469,6 +466,21 @@
         mBarView.disableHwLayers();
     }
 
+    /** Sets the stubbed state of this task view. */
+    void setStubState(boolean isStub) {
+        if (!mIsStub && isStub) {
+            // This is now a stub task view, so clip to the bar height, hide the thumbnail
+            setClipBounds(new Rect(0, 0, getMeasuredWidth(), mBarView.getMeasuredHeight()));
+            mThumbnailView.setVisibility(View.INVISIBLE);
+            // Temporary
+            mBarView.mActivityDescription.setText("Stub");
+        } else if (mIsStub && !isStub) {
+            setClipBounds(null);
+            mThumbnailView.setVisibility(View.VISIBLE);
+        }
+        mIsStub = isStub;
+    }
+
     /**
      * Returns whether this view should be clipped, or any views below should clip against this
      * view.
@@ -573,7 +585,9 @@
             mThumbnailView.rebindToTask(mTask);
             mBarView.rebindToTask(mTask);
             // Rebind any listeners
-            mBarView.mApplicationIcon.setOnClickListener(this);
+            if (Constants.DebugFlags.App.EnableTaskFiltering) {
+                mBarView.mApplicationIcon.setOnClickListener(this);
+            }
             mBarView.mDismissButton.setOnClickListener(this);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 if (mConfig.developerOptionsEnabled) {
@@ -592,7 +606,9 @@
             mThumbnailView.unbindFromTask();
             mBarView.unbindFromTask();
             // Unbind any listeners
-            mBarView.mApplicationIcon.setOnClickListener(null);
+            if (Constants.DebugFlags.App.EnableTaskFiltering) {
+                mBarView.mApplicationIcon.setOnClickListener(null);
+            }
             mBarView.mDismissButton.setOnClickListener(null);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 mBarView.mApplicationIcon.setOnLongClickListener(null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
index fa97d2c..ec24198 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.views;
 
 import android.graphics.Rect;
-import com.android.systemui.recents.ReferenceCountedTrigger;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 
 /* Common code related to view animations */
 public class ViewAnimation {