Intermediate refactoring to move towards in-app view transitions.

- Fixing bug where we weren't toggling to the right task when using affiliations
- Refactoring task rect calculation to allow full screen task view to be laid out for transitions
- Refactoring the view bounds animations into a separate class
- Refactoring the footer view (for lock-to-task) out of TaskView
- Refactoring some transform code out of TaskView
- Removing fullscreen overlay view
- Fixing case where extra invalidations and layouts were still happening in FixedSizeImageView
- Adding debug overlay to replace specific debug drawing code

Change-Id: Ibf98b6a0782a68cd84582203c807cece1ff3379f
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 66e5d14..591149c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -70,10 +71,13 @@
 
     // Task launching
     RecentsConfiguration mConfig;
-    Rect mWindowRect;
-    Rect mTaskStackBounds;
+    Rect mWindowRect = new Rect();
+    Rect mTaskStackBounds = new Rect();
+    Rect mSystemInsets = new Rect();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
     int mStatusBarHeight;
+    int mNavBarHeight;
+    int mNavBarWidth;
 
     // Variables to keep track of if we need to start recents after binding
     View mStatusBarView;
@@ -81,15 +85,23 @@
     long mLastToggleTime;
 
     public AlternateRecentsComponent(Context context) {
+        Resources res = context.getResources();
         mContext = context;
         mSystemServicesProxy = new SystemServicesProxy(context);
         mHandler = new Handler();
         mConfig = RecentsConfiguration.reinitialize(context, mSystemServicesProxy);
         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);
+        mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+        mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
+        mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
+        mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
+                mNavBarWidth, mTaskStackBounds);
+        if (mConfig.isLandscape && mConfig.transposeRecentsLayoutWithOrientation) {
+            mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
+        } else {
+            mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
+        }
     }
 
     public void onStart() {
@@ -150,7 +162,13 @@
         mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
         mConfig.updateOnConfigurationChange();
         mWindowRect = mSystemServicesProxy.getWindowRect();
-        mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mTaskStackBounds);
+        mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
+                mNavBarWidth, mTaskStackBounds);
+        if (mConfig.isLandscape && mConfig.transposeRecentsLayoutWithOrientation) {
+            mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
+        } else {
+            mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
+        }
         sLastScreenshot = null;
     }
 
@@ -301,7 +319,9 @@
         // Get the stack
         TaskStackView tsv = new TaskStackView(mContext, stack);
         TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
-        tsv.computeRects(mTaskStackBounds.width(), mTaskStackBounds.height() - mStatusBarHeight, 0, 0);
+        Rect taskStackBounds = new Rect(mTaskStackBounds);
+        taskStackBounds.bottom -= mSystemInsets.bottom;
+        tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
         tsv.setStackScrollToInitialState();
 
         // Find the running task in the TaskStack
@@ -325,8 +345,6 @@
 
         // Get the transform for the running task
         mTmpTransform = algo.getStackTransform(task, tsv.getStackScroll(), mTmpTransform);
-        mTmpTransform.rect.offset(mTaskStackBounds.left, mTaskStackBounds.top);
-        mTmpTransform.rect.offset(0, mStatusBarHeight);
         return new Rect(mTmpTransform.rect);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 5741f22..7d69b94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -32,18 +32,20 @@
 import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.widget.FrameLayout;
 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.Task;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.FullscreenTransitionOverlayView;
+import com.android.systemui.recents.views.DebugOverlayView;
 import com.android.systemui.recents.views.RecentsView;
 import com.android.systemui.recents.views.SystemBarScrimViews;
 import com.android.systemui.recents.views.ViewAnimation;
@@ -56,8 +58,7 @@
  * The main Recents activity that is started from AlternateRecentsComponent.
  */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
-        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
-        FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
+        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
 
     // Actions and Extras sent from AlternateRecentsComponent
     final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
@@ -73,17 +74,14 @@
     SystemBarScrimViews mScrimViews;
     ViewStub mEmptyViewStub;
     View mEmptyView;
-    ViewStub mFullscreenOverlayStub;
-    FullscreenTransitionOverlayView mFullScreenOverlayView;
+    DebugOverlayView mDebugOverlay;
 
     // Search AppWidget
     RecentsAppWidgetHost mAppWidgetHost;
     AppWidgetProviderInfo mSearchAppWidgetInfo;
     AppWidgetHostView mSearchAppWidgetHostView;
 
-
     // Runnables to finish the Recents activity
-    FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable();
     FinishRecentsRunnable mFinishLaunchHomeRunnable;
 
     /**
@@ -97,10 +95,6 @@
         Intent mLaunchIntent;
         ActivityOptions mLaunchOpts;
 
-        public FinishRecentsRunnable() {
-            // Do nothing
-        }
-
         /**
          * Creates a finish runnable that starts the specified intent, using the given
          * ActivityOptions.
@@ -151,8 +145,7 @@
             } 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(
-                        mFullScreenOverlayView, t));
+                mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(t));
                 onEnterAnimationTriggered();
             }
         }
@@ -187,11 +180,12 @@
 
     /** Updates the set of recent tasks */
     void updateRecentsTasks(Intent launchIntent) {
+        // Load all the tasks
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
         ArrayList<TaskStack> stacks = root.getStacks();
         if (!stacks.isEmpty()) {
-            mRecentsView.setBSP(root);
+            mRecentsView.setTaskStacks(root.getStacks());
         }
 
         // Update the configuration based on the launch intent
@@ -207,6 +201,23 @@
         mConfig.launchedToTaskId = launchIntent.getIntExtra(
                 AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_TASK_ID, -1);
 
+        // Mark the task that is the launch target
+        int taskStackCount = stacks.size();
+        if (mConfig.launchedToTaskId != -1) {
+            for (int i = 0; i < taskStackCount; i++) {
+                TaskStack stack = stacks.get(i);
+                ArrayList<Task> tasks = stack.getTasks();
+                int taskCount = tasks.size();
+                for (int j = 0; j < taskCount; j++) {
+                    Task t = tasks.get(j);
+                    if (t.key.id == mConfig.launchedToTaskId) {
+                        t.isLaunchTarget = true;
+                        break;
+                    }
+                }
+            }
+        }
+
         // Update the top level view's visibilities
         if (mConfig.launchedWithNoRecentTasks) {
             if (mEmptyView == null) {
@@ -286,9 +297,6 @@
     /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
     boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
         if (mVisible) {
-            // If we are mid-animation into Recents, reverse the animation now
-            if (mFullScreenOverlayView != null &&
-                mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
             // If we currently have filtered stacks, then unfilter those first
             if (checkFilteredStackState &&
                 mRecentsView.unfilterFilteredStacks()) return true;
@@ -299,8 +307,8 @@
                 dismissRecentsToHomeRaw(true);
                 return true;
             }
-            // Otherwise, try and return to the first Task in the stack
-            if (mRecentsView.launchFirstTask()) return true;
+            // Otherwise, try and return to the Task that Recents was launched from
+            if (mRecentsView.launchPreviousTask()) return true;
             // If none of the other cases apply, then just go Home
             dismissRecentsToHomeRaw(true);
             return true;
@@ -323,9 +331,6 @@
     /** Dismisses Recents directly to Home if we currently aren't transitioning. */
     boolean dismissRecentsToHome(boolean animated) {
         if (mVisible) {
-            // If we are mid-animation into Recents, reverse the animation now
-            if (mFullScreenOverlayView != null &&
-                mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
             // Return to Home
             dismissRecentsToHomeRaw(animated);
             return true;
@@ -363,8 +368,8 @@
                 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
-        mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub);
         mScrimViews = new SystemBarScrimViews(this, mConfig);
+        inflateDebugOverlay();
 
         // Bind the search app widget when we first start up
         bindSearchBarAppWidget();
@@ -390,13 +395,6 @@
             e.printStackTrace();
         }
 
-        // Prepare the screenshot transition if necessary
-        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
-            mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
-            mFullScreenOverlayView.setCallbacks(this);
-            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
-        }
-
         // Update if we are getting a configuration change
         if (savedInstanceState != null) {
             mConfig.updateOnConfigurationChange();
@@ -404,6 +402,19 @@
         }
     }
 
+    /** Inflates the debug overlay if debug mode is enabled. */
+    void inflateDebugOverlay() {
+        if (mConfig.debugModeEnabled && mDebugOverlay == null) {
+            ViewGroup parent = (ViewGroup) findViewById(android.R.id.content).getRootView();
+            mDebugOverlay = new DebugOverlayView(this);
+            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT);
+            parent.addView(mDebugOverlay, lp);
+            mRecentsView.setDebugOverlay(mDebugOverlay);
+        }
+    }
+
     void onConfigurationChange() {
         // Update RecentsConfiguration
         mConfig = RecentsConfiguration.reinitialize(this,
@@ -411,8 +422,7 @@
 
         // Try and start the enter animation (or restart it on configuration changed)
         ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
-        mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
-                mFullScreenOverlayView, t));
+        mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(t));
         onEnterAnimationTriggered();
     }
 
@@ -421,13 +431,13 @@
         super.onNewIntent(intent);
         setIntent(intent);
 
+        // Clear any debug rects
+        if (mDebugOverlay != null) {
+            mDebugOverlay.clear();
+        }
+
         // Update the recent tasks
         updateRecentsTasks(intent);
-
-        // Prepare the screenshot transition if necessary
-        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
-            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
-        }
     }
 
     @Override
@@ -531,17 +541,25 @@
 
     /** Called when debug mode is triggered */
     public void onDebugModeTriggered() {
+
         if (mConfig.developerOptionsEnabled) {
             SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
             if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
                 // Disable the debug mode
                 settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+                mConfig.debugModeEnabled = false;
+                inflateDebugOverlay();
+                mDebugOverlay.disable();
             } else {
                 // Enable the debug mode
                 settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+                mConfig.debugModeEnabled = true;
+                inflateDebugOverlay();
+                mDebugOverlay.enable();
             }
-            Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion +
-                    ") toggled, please restart Recents now", Toast.LENGTH_SHORT).show();
+            Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " +
+                (mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now",
+                Toast.LENGTH_SHORT).show();
         }
     }
 
@@ -551,19 +569,6 @@
         mScrimViews.startEnterRecentsAnimation();
     }
 
-    /**** FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks Implementation ****/
-
-    @Override
-    public void onEnterAnimationComplete() {
-        // Reset the full screenshot transition view
-        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
-            mFullScreenOverlayView.reset();
-
-            // Recycle the full screen screenshot
-            AlternateRecentsComponent.consumeLastScreenshot();
-        }
-    }
-
     /**** RecentsView.RecentsViewCallbacks Implementation ****/
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index a0cab5c..55711cf0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -309,19 +309,16 @@
      * Returns the task stack bounds in the current orientation. These bounds do not account for
      * the system insets.
      */
-    public void getTaskStackBounds(int width, int height, Rect taskStackBounds) {
-        if (hasSearchBarAppWidget()) {
-            Rect searchBarBounds = new Rect();
-            getSearchBarBounds(width, height, searchBarBounds);
-            if (isLandscape && transposeRecentsLayoutWithOrientation) {
-                // In landscape, the search bar appears on the left, so shift the task rect right
-                taskStackBounds.set(searchBarBounds.width(), 0, width, height);
-            } else {
-                // In portrait, the search bar appears on the top, so shift the task rect below
-                taskStackBounds.set(0, searchBarBounds.height(), width, height);
-            }
+    public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset,
+                                   Rect taskStackBounds) {
+        Rect searchBarBounds = new Rect();
+        getSearchBarBounds(windowWidth, windowHeight, topInset, searchBarBounds);
+        if (isLandscape && transposeRecentsLayoutWithOrientation) {
+            // In landscape, the search bar appears on the left
+            taskStackBounds.set(searchBarBounds.right, topInset, windowWidth - rightInset, windowHeight);
         } else {
-            taskStackBounds.set(0, 0, width, height);
+            // In portrait, the search bar appears on the top (which already has the inset)
+            taskStackBounds.set(0, searchBarBounds.bottom, windowWidth, windowHeight);
         }
     }
 
@@ -329,19 +326,20 @@
      * Returns the search bar bounds in the current orientation.  These bounds do not account for
      * the system insets.
      */
-    public void getSearchBarBounds(int width, int height, Rect searchBarSpaceBounds) {
+    public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset,
+                                   Rect searchBarSpaceBounds) {
         // Return empty rects if search is not enabled
-        if (!Constants.DebugFlags.App.EnableSearchLayout) {
-            searchBarSpaceBounds.set(0, 0, 0, 0);
-            return;
+        int searchBarSize = searchBarSpaceHeightPx;
+        if (!Constants.DebugFlags.App.EnableSearchLayout || !hasSearchBarAppWidget()) {
+            searchBarSize = 0;
         }
 
         if (isLandscape && transposeRecentsLayoutWithOrientation) {
             // In landscape, the search bar appears on the left
-            searchBarSpaceBounds.set(0, 0, searchBarSpaceHeightPx, height);
+            searchBarSpaceBounds.set(0, topInset, searchBarSize, windowHeight);
         } else {
             // In portrait, the search bar appears on the top
-            searchBarSpaceBounds.set(0, 0, width, searchBarSpaceHeightPx);
+            searchBarSpaceBounds.set(0, topInset, windowWidth, topInset + searchBarSize);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 3b436f0..fb77751 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -38,6 +38,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
@@ -437,7 +438,9 @@
         Rect windowRect = new Rect();
         if (mWm == null) return windowRect;
 
-        mWm.getDefaultDisplay().getRectSize(windowRect);
+        Point p = new Point();
+        mWm.getDefaultDisplay().getRealSize(p);
+        windowRect.set(0, 0, p.x, p.y);
         return windowRect;
     }
 
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 4cf9235..41874fc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -77,6 +77,7 @@
     public TaskKey key;
     public TaskGrouping group;
     public int taskAffiliation;
+    public boolean isLaunchTarget;
     public Drawable applicationIcon;
     public Drawable activityIcon;
     public String activityLabel;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
new file mode 100644
index 0000000..9076818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.ObjectAnimator;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import com.android.systemui.recents.RecentsConfiguration;
+
+/* An outline provider that has a clip and outline that can be animated. */
+public class AnimateableViewBounds extends ViewOutlineProvider {
+
+    RecentsConfiguration mConfig;
+
+    View mSourceView;
+    Rect mClipRect = new Rect();
+    Rect mOutlineClipRect = new Rect();
+    int mCornerRadius;
+
+    ObjectAnimator mClipTopAnimator;
+    ObjectAnimator mClipBottomAnimator;
+
+    public AnimateableViewBounds(View source, int cornerRadius) {
+        mConfig = RecentsConfiguration.getInstance();
+        mSourceView = source;
+        mCornerRadius = cornerRadius;
+        mSourceView.setClipToOutline(true);
+        setClipTop(getClipTop());
+        setClipBottom(getClipBottom());
+        setOutlineClipBottom(getOutlineClipBottom());
+    }
+
+    @Override
+    public void getOutline(View view, Outline outline) {
+        outline.setRoundRect(Math.max(mClipRect.left, mOutlineClipRect.left),
+                Math.max(mClipRect.top, mOutlineClipRect.top),
+                mSourceView.getMeasuredWidth() - Math.max(mClipRect.right, mOutlineClipRect.right),
+                mSourceView.getMeasuredHeight() - Math.max(mClipRect.bottom, mOutlineClipRect.bottom),
+                mCornerRadius);
+    }
+
+    /** Animates the top clip. */
+    void animateClipTop(int top, int duration) {
+        if (mClipTopAnimator != null) {
+            mClipTopAnimator.removeAllListeners();
+            mClipTopAnimator.cancel();
+        }
+        mClipTopAnimator = ObjectAnimator.ofInt(this, "clipTop", top);
+        mClipTopAnimator.setDuration(duration);
+        mClipTopAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+        mClipTopAnimator.start();
+    }
+
+    /** Sets the top clip. */
+    public void setClipTop(int top) {
+        if (top != mClipRect.top) {
+            mClipRect.top = top;
+            mSourceView.invalidateOutline();
+        }
+    }
+
+    /** Returns the top clip. */
+    public int getClipTop() {
+        return mClipRect.top;
+    }
+
+    /** Animates the bottom clip. */
+    void animateClipBottom(int bottom, int duration) {
+        if (mClipTopAnimator != null) {
+            mClipTopAnimator.removeAllListeners();
+            mClipTopAnimator.cancel();
+        }
+        mClipTopAnimator = ObjectAnimator.ofInt(this, "clipBottom", bottom);
+        mClipTopAnimator.setDuration(duration);
+        mClipTopAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+        mClipTopAnimator.start();
+    }
+
+    /** Sets the bottom clip. */
+    public void setClipBottom(int bottom) {
+        if (bottom != mClipRect.bottom) {
+            mClipRect.bottom = bottom;
+            mSourceView.invalidateOutline();
+        }
+    }
+
+    /** Returns the bottom clip. */
+    public int getClipBottom() {
+        return mClipRect.bottom;
+    }
+
+    public void setOutlineClipBottom(int bottom) {
+        if (bottom != mOutlineClipRect.bottom) {
+            mOutlineClipRect.bottom = bottom;
+            mSourceView.invalidateOutline();
+        }
+    }
+
+    public int getOutlineClipBottom() {
+        return mOutlineClipRect.bottom;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java
new file mode 100644
index 0000000..6c90fe3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.Pair;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+/**
+ * A full screen overlay layer that allows us to draw views from throughout the system on the top
+ * most layer.
+ */
+public class DebugOverlayView extends FrameLayout {
+
+    final static int sCornerRectSize = 50;
+
+    ArrayList<Pair<Rect, Integer>> mRects = new ArrayList<Pair<Rect, Integer>>();
+    Paint mDebugOutline = new Paint();
+    Paint mTmpPaint = new Paint();
+    boolean mEnabled = true;
+
+    public DebugOverlayView(Context context) {
+        super(context);
+        mDebugOutline.setColor(0xFFff0000);
+        mDebugOutline.setStyle(Paint.Style.STROKE);
+        mDebugOutline.setStrokeWidth(8f);
+        setWillNotDraw(false);
+    }
+
+    /** Enables the debug overlay drawing. */
+    public void enable() {
+        mEnabled = true;
+        invalidate();
+    }
+
+    /** Disables the debug overlay drawing. */
+    public void disable() {
+        mEnabled = false;
+        invalidate();
+    }
+
+    /** Clears all debug rects. */
+    public void clear() {
+        mRects.clear();
+    }
+
+    /** Adds a rect to be drawn. */
+    void addRect(Rect r, int color) {
+        mRects.add(new Pair<Rect, Integer>(r, color));
+        invalidate();
+    }
+
+    /** Adds a view's global rect to be drawn. */
+    void addViewRect(View v, int color) {
+        Rect vr = new Rect();
+        v.getGlobalVisibleRect(vr);
+        mRects.add(new Pair<Rect, Integer>(vr, color));
+        invalidate();
+    }
+
+    /** Adds a rect, relative to a given view to be drawn. */
+    void addRectRelativeToView(View v, Rect r, int color) {
+        Rect vr = new Rect();
+        v.getGlobalVisibleRect(vr);
+        r.offsetTo(vr.left, vr.top);
+        mRects.add(new Pair<Rect, Integer>(r, color));
+        invalidate();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        addRect(new Rect(0, 0, sCornerRectSize, sCornerRectSize), 0xFFff0000);
+        addRect(new Rect(getMeasuredWidth() - sCornerRectSize, getMeasuredHeight() - sCornerRectSize,
+                getMeasuredWidth(), getMeasuredHeight()), 0xFFff0000);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mEnabled) {
+            // Draw the outline
+            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugOutline);
+
+            // Draw the rects
+            int numRects = mRects.size();
+            for (int i = 0; i < numRects; i++) {
+                Pair<Rect, Integer> r = mRects.get(i);
+                mTmpPaint.setColor(r.second);
+                canvas.drawRect(r.first, mTmpPaint);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
index 3adee0ea..4b5c0bd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
@@ -17,6 +17,7 @@
 package com.android.systemui.recents.views;
 
 import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.widget.ImageView;
@@ -27,8 +28,6 @@
  */
 public class FixedSizeImageView extends ImageView {
 
-    int mFixedWidth;
-    int mFixedHeight;
     boolean mAllowRelayout = true;
     boolean mAllowInvalidate = true;
 
@@ -49,13 +48,6 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mFixedWidth = getMeasuredWidth();
-        mFixedHeight = getMeasuredHeight();
-    }
-
-    @Override
     public void requestLayout() {
         if (mAllowRelayout) {
             super.requestLayout();
@@ -71,7 +63,9 @@
 
     @Override
     public void setImageDrawable(Drawable drawable) {
-        if (drawable == null || (mFixedWidth > 0 && mFixedHeight > 0)) {
+        boolean isNullBitmapDrawable = (drawable instanceof BitmapDrawable) &&
+                (((BitmapDrawable) drawable).getBitmap() == null);
+        if (drawable == null || isNullBitmapDrawable) {
             mAllowRelayout = false;
             mAllowInvalidate = false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
deleted file mode 100644
index 63f59be..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import com.android.systemui.R;
-import com.android.systemui.recents.RecentsConfiguration;
-
-
-/**
- * The full screen transition view that gets animated down from the full screen into a task
- * thumbnail view.
- */
-public class FullscreenTransitionOverlayView extends FrameLayout {
-
-    /** The FullscreenTransitionOverlayView callbacks */
-    public interface FullScreenTransitionViewCallbacks {
-        void onEnterAnimationComplete();
-    }
-
-    RecentsConfiguration mConfig;
-
-    FullScreenTransitionViewCallbacks mCb;
-
-    ImageView mScreenshotView;
-    Rect mClipRect = new Rect();
-    Paint mLayerPaint = new Paint();
-    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY);
-
-    int mDim;
-    int mMaxDim;
-    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
-
-    boolean mIsAnimating;
-    AnimatorSet mEnterAnimation;
-
-    public FullscreenTransitionOverlayView(Context context) {
-        super(context);
-    }
-
-    public FullscreenTransitionOverlayView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr,
-                                           int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mConfig = RecentsConfiguration.getInstance();
-        mMaxDim = mConfig.taskStackMaxDim;
-        setClipTop(getClipTop());
-        setClipBottom(getClipBottom());
-        setDim(getDim());
-        setWillNotDraw(false);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        mScreenshotView = (ImageView) findViewById(R.id.image);
-    }
-
-    /** Sets the callbacks */
-    public void setCallbacks(FullScreenTransitionViewCallbacks cb) {
-        mCb = cb;
-    }
-
-    /** Sets the top clip */
-    public void setClipTop(int clip) {
-        mClipRect.top = clip;
-        setClipBounds(mClipRect);
-    }
-
-    /** Gets the top clip */
-    public int getClipTop() {
-        return mClipRect.top;
-    }
-
-    /** Sets the bottom clip */
-    public void setClipBottom(int clip) {
-        mClipRect.bottom = clip;
-        setClipBounds(mClipRect);
-    }
-
-    /** Gets the top clip */
-    public int getClipBottom() {
-        return mClipRect.bottom;
-    }
-
-    /** Returns the current dim. */
-    public void setDim(int dim) {
-        mDim = dim;
-        /*
-        int inverse = 255 - mDim;
-        mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse));
-        mLayerPaint.setColorFilter(mDimColorFilter);
-        setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint);
-        */
-    }
-
-    /** Returns the current dim. */
-    public int getDim() {
-        return mDim;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    /** Prepares the screenshot view for the transition into Recents */
-    public void prepareAnimateOnEnterRecents(Bitmap screenshot) {
-        if (!mConfig.launchedFromAppWithScreenshot) return;
-
-        setClipTop(0);
-        setClipBottom(getMeasuredHeight());
-        setDim(0);
-        setTranslationY(0f);
-        setScaleX(1f);
-        setScaleY(1f);
-        setVisibility(mConfig.launchedFromAppWithScreenshot ? View.VISIBLE : View.INVISIBLE);
-        if (screenshot != null) {
-            mScreenshotView.setImageBitmap(screenshot);
-        } else {
-            mScreenshotView.setImageDrawable(null);
-        }
-    }
-
-    /** Resets the transition view */
-    public void reset() {
-        setVisibility(View.GONE);
-        mScreenshotView.setImageDrawable(null);
-    }
-
-    /** Animates this view as it enters recents */
-    public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx,
-                                      final Runnable postAnimRunnable) {
-        // Cancel the current animation
-        if (mEnterAnimation != null) {
-            mEnterAnimation.removeAllListeners();
-            mEnterAnimation.cancel();
-        }
-
-        // Calculate the bottom clip
-        Rect targetTaskRect = ctx.targetTaskTransform.rect;
-        float scale = (float) targetTaskRect.width() / getMeasuredWidth();
-        float scaleYOffset = ((1f - scale) * getMeasuredHeight()) / 2;
-        float scaledTopInset = (int) (scale * mConfig.systemInsets.top);
-        int translationY = (int) -scaleYOffset + (int) (mConfig.systemInsets.top - scaledTopInset)
-                + targetTaskRect.top;
-        int clipBottom = mConfig.systemInsets.top + (int) (targetTaskRect.height() / scale);
-
-        // Calculate the dim
-        float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale;
-        float scaleRange = 1f - minScale;
-        float dim = (1f - ctx.targetTaskTransform.scale) / scaleRange;
-        dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
-        int toDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
-
-        // Enable the HW Layers on the screenshot view
-        mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
-
-        // Compose the animation
-        mEnterAnimation = new AnimatorSet();
-        mEnterAnimation.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Mark that we are no longer animating
-                        mIsAnimating = false;
-                        // Disable the HW Layers on this view
-                        setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
-                        // Notify any callbacks
-                        mCb.onEnterAnimationComplete();
-                        // Run the given post-anim runnable
-                        postAnimRunnable.run();
-                    }
-                });
-            }
-        });
-        // XXX: Translation y should be negative initially to simulate moving from the top of the screen?
-        mEnterAnimation.setStartDelay(0);
-        mEnterAnimation.setDuration(475);
-        mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator);
-        mEnterAnimation.playTogether(
-                // ObjectAnimator.ofInt(this, "clipTop", mConfig.systemInsets.top),
-                ObjectAnimator.ofInt(this, "clipBottom", clipBottom),
-                ObjectAnimator.ofInt(this, "dim", toDim),
-                ObjectAnimator.ofFloat(this, "translationY", translationY),
-                ObjectAnimator.ofFloat(this, "scaleX", scale),
-                ObjectAnimator.ofFloat(this, "scaleY", scale)
-        );
-        setClipTop(mConfig.systemInsets.top);
-        mEnterAnimation.start();
-
-        mIsAnimating = true;
-    }
-
-    /** Animates this view back out of Recents if we were in the process of animating in. */
-    public boolean cancelAnimateOnEnterRecents(final Runnable postAnimRunnable) {
-        if (mIsAnimating) {
-            // Cancel the current animation
-            if (mEnterAnimation != null) {
-                mEnterAnimation.removeAllListeners();
-                mEnterAnimation.cancel();
-            }
-
-            // Enable the HW Layers on the screenshot view
-            mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
-
-            // Compose the animation
-            mEnterAnimation = new AnimatorSet();
-            mEnterAnimation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    post(new Runnable() {
-                        @Override
-                        public void run() {
-                            // Mark that we are no longer animating
-                            mIsAnimating = false;
-                            // Disable the HW Layers on the screenshot view
-                            mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
-                            // Notify any callbacks
-                            mCb.onEnterAnimationComplete();
-                            // Run the given post-anim runnable
-                            postAnimRunnable.run();
-                        }
-                    });
-                }
-            });
-            mEnterAnimation.setDuration(475);
-            mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator);
-            mEnterAnimation.playTogether(
-                    ObjectAnimator.ofInt(this, "clipTop", 0),
-                    ObjectAnimator.ofInt(this, "clipBottom", getMeasuredHeight()),
-                    ObjectAnimator.ofInt(this, "dim", 0),
-                    ObjectAnimator.ofFloat(this, "translationY", 0f),
-                    ObjectAnimator.ofFloat(this, "scaleX", 1f),
-                    ObjectAnimator.ofFloat(this, "scaleY", 1f)
-            );
-            mEnterAnimation.start();
-
-            return true;
-        }
-        return false;
-    }
-}
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 b6479db..356841f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -24,7 +24,6 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.provider.Settings;
@@ -40,7 +39,6 @@
 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.SpaceNode;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
@@ -63,13 +61,10 @@
 
     RecentsConfiguration mConfig;
     LayoutInflater mInflater;
-    Paint mDebugModePaint;
+    DebugOverlayView mDebugOverlay;
 
-    // The space partitioning root of this container
-    SpaceNode mBSP;
-    // Search bar view
+    ArrayList<TaskStack> mStacks;
     View mSearchBar;
-    // Recents view callbacks
     RecentsViewCallbacks mCb;
 
     public RecentsView(Context context) {
@@ -95,10 +90,13 @@
         mCb = cb;
     }
 
-    /** Set/get the bsp root node */
-    public void setBSP(SpaceNode n) {
-        mBSP = n;
+    /** Sets the debug overlay */
+    public void setDebugOverlay(DebugOverlayView overlay) {
+        mDebugOverlay = overlay;
+    }
 
+    /** Set/get the bsp root node */
+    public void setTaskStacks(ArrayList<TaskStack> stacks) {
         // Remove all TaskStackViews (but leave the search bar)
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
@@ -109,21 +107,18 @@
         }
 
         // Create and add all the stacks for this partition of space.
-        ArrayList<TaskStack> stacks = mBSP.getStacks();
-        for (TaskStack stack : stacks) {
+        mStacks = stacks;
+        int numStacks = mStacks.size();
+        for (int i = 0; i < numStacks; i++) {
+            TaskStack stack = mStacks.get(i);
             TaskStackView stackView = new TaskStackView(getContext(), stack);
             stackView.setCallbacks(this);
+            // Enable debug mode drawing
+            if (mConfig.debugModeEnabled) {
+                stackView.setDebugOverlay(mDebugOverlay);
+            }
             addView(stackView);
         }
-
-        // Enable debug mode drawing
-        if (mConfig.debugModeEnabled) {
-            mDebugModePaint = new Paint();
-            mDebugModePaint.setColor(0xFFff0000);
-            mDebugModePaint.setStyle(Paint.Style.STROKE);
-            mDebugModePaint.setStrokeWidth(5f);
-            setWillNotDraw(false);
-        }
     }
 
     /** Launches the focused task from the first stack if possible */
@@ -150,8 +145,8 @@
         return false;
     }
 
-    /** Launches the first task from the first stack if possible */
-    public boolean launchFirstTask() {
+    /** Launches the task that Recents was launched from, if possible */
+    public boolean launchPreviousTask() {
         // Get the first stack view
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -161,20 +156,17 @@
                 TaskStack stack = stackView.mStack;
                 ArrayList<Task> tasks = stack.getTasks();
 
-                // Get the first task in the stack
+                // Find the launch task in the stack
                 if (!tasks.isEmpty()) {
-                    Task task = tasks.get(tasks.size() - 1);
-                    TaskView tv = null;
-
-                    // Try and use the first child task view as the source of the launch animation
-                    if (stackView.getChildCount() > 0) {
-                        TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1);
-                        if (stv.getTask() == task) {
-                            tv = stv;
+                    int taskCount = tasks.size();
+                    for (int j = 0; j < taskCount; j++) {
+                        if (tasks.get(j).isLaunchTarget) {
+                            Task task = tasks.get(j);
+                            TaskView tv = stackView.getChildViewForTask(task);
+                            onTaskViewClicked(stackView, tv, stack, task, false);
+                            return true;
                         }
                     }
-                    onTaskViewClicked(stackView, tv, stack, task, false);
-                    return true;
                 }
             }
         }
@@ -242,35 +234,31 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = MeasureSpec.getSize(widthMeasureSpec);
-        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
-        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 
         // Get the search bar bounds and measure the search bar layout
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
-            mConfig.getSearchBarBounds(width, height - mConfig.systemInsets.top, searchBarSpaceBounds);
+            mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds);
             mSearchBar.measure(
                     MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
         }
 
-        // We give the full width of the space, not including the right nav bar insets in landscape,
-        // to the stack view, since we want the tasks to render under the search bar in landscape.
-        // In addition, we give it the full height, not including the top inset or search bar space,
-        // since we want the tasks to render under the navigation buttons in portrait.
         Rect taskStackBounds = new Rect();
-        mConfig.getTaskStackBounds(width, height, taskStackBounds);
-        int childWidth = width - mConfig.systemInsets.right;
-        int childHeight = taskStackBounds.height() - mConfig.systemInsets.top;
+        mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top,
+                mConfig.systemInsets.right, taskStackBounds);
 
-        // Measure each TaskStackView
+        // Measure each TaskStackView with the full width and height of the window since the 
+        // transition view is a child of that stack view
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child != mSearchBar && child.getVisibility() != GONE) {
-                child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode),
-                        MeasureSpec.makeMeasureSpec(childHeight, heightMode));
+                TaskStackView tsv = (TaskStackView) child;
+                // Set the insets to be the top/left inset + search bounds
+                tsv.setStackInsetRect(taskStackBounds);
+                tsv.measure(widthMeasureSpec, heightMeasureSpec);
             }
         }
 
@@ -285,49 +273,30 @@
         // Get the search bar bounds so that we lay it out
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
-            mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds);
-            mSearchBar.layout(mConfig.systemInsets.left + searchBarSpaceBounds.left,
-                    mConfig.systemInsets.top + searchBarSpaceBounds.top,
-                    mConfig.systemInsets.left + mSearchBar.getMeasuredWidth(),
-                    mConfig.systemInsets.top + mSearchBar.getMeasuredHeight());
+            mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
+                    mConfig.systemInsets.top, searchBarSpaceBounds);
+            mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
+                    searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
         }
 
-        // We offset the stack view by the left inset (if any), but lay it out under the search bar.
-        // In addition, we offset our stack views by the top inset and search bar height, but not
-        // the bottom insets because we want it to render under the navigation buttons.
-        Rect taskStackBounds = new Rect();
-        mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
-        left += mConfig.systemInsets.left;
-        top += mConfig.systemInsets.top + taskStackBounds.top;
-
-        // Layout each child
-        // XXX: Based on the space node for that task view
+        // Layout each TaskStackView with the full width and height of the window since the 
+        // transition view is a child of that stack view
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child != mSearchBar && child.getVisibility() != GONE) {
-                TaskStackView tsv = (TaskStackView) child;
-                child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight());
+                child.layout(left, top, left + child.getMeasuredWidth(),
+                        top + child.getMeasuredHeight());
             }
         }
     }
 
     @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        // Debug mode drawing
-        if (mConfig.debugModeEnabled) {
-            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugModePaint);
-        }
-    }
-
-    @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         // Update the configuration with the latest system insets and trigger a relayout
         mConfig.updateSystemInsets(insets.getSystemWindowInsets());
         requestLayout();
-
-        return insets.consumeSystemWindowInsets(false, false, false, true);
+        return insets.consumeSystemWindowInsets();
     }
 
     /** Notifies each task view of the user interaction. */
@@ -364,11 +333,12 @@
 
     /** Unfilters any filtered stacks */
     public boolean unfilterFilteredStacks() {
-        if (mBSP != null) {
+        if (mStacks != null) {
             // Check if there are any filtered stacks and unfilter them before we back out of Recents
             boolean stacksUnfiltered = false;
-            ArrayList<TaskStack> stacks = mBSP.getStacks();
-            for (TaskStack stack : stacks) {
+            int numStacks = mStacks.size();
+            for (int i = 0; i < numStacks; i++) {
+                TaskStack stack = mStacks.get(i);
                 if (stack.hasFilteredTasks()) {
                     stack.unfilterTasks();
                     stacksUnfiltered = true;
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 deb9df3..fbfad5c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -43,8 +43,6 @@
 
     RecentsConfiguration mConfig;
 
-    Task mTask;
-
     ImageView mDismissButton;
     ImageView mApplicationIcon;
     TextView mActivityDescription;
@@ -52,6 +50,8 @@
     Drawable mLightDismissDrawable;
     Drawable mDarkDismissDrawable;
 
+    boolean mIsFullscreen;
+
     Paint mLayerPaint = new Paint();
     static Paint sHighlightPaint;
 
@@ -113,11 +113,18 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        // Draw the highlight at the top edge (but put the bottom edge just out of view)
-        float offset = mConfig.taskViewHighlightPx / 2f;
-        float radius = mConfig.taskViewRoundedCornerRadiusPx;
-        canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
-                getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
+        if (!mIsFullscreen) {
+            // Draw the highlight at the top edge (but put the bottom edge just out of view)
+            float offset = mConfig.taskViewHighlightPx / 2f;
+            float radius = mConfig.taskViewRoundedCornerRadiusPx;
+            canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
+                    getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
+        }
+    }
+
+    /** Sets whether the current task is full screen or not. */
+    void setIsFullscreen(boolean isFullscreen) {
+        mIsFullscreen = isFullscreen;
     }
 
     /** Synchronizes this bar view's properties with the task's transform */
@@ -149,7 +156,6 @@
 
     /** Binds the bar view to the task */
     void rebindToTask(Task t) {
-        mTask = t;
         // If an activity icon is defined, then we use that as the primary icon to show in the bar,
         // otherwise, we fall back to the application icon
         if (t.activityIcon != null) {
@@ -170,7 +176,6 @@
 
     /** Unbinds the bar view from the task */
     void unbindFromTask() {
-        mTask = null;
         mApplicationIcon.setImageDrawable(null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java
new file mode 100644
index 0000000..95af1c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import com.android.systemui.recents.RecentsConfiguration;
+
+
+/** The task footer view */
+public class TaskFooterView extends FrameLayout {
+
+    interface TaskFooterViewCallbacks {
+        public void onTaskFooterHeightChanged(int height, int maxHeight);
+    }
+
+    RecentsConfiguration mConfig;
+
+    TaskFooterViewCallbacks mCb;
+    int mFooterHeight;
+    int mMaxFooterHeight;
+    ObjectAnimator mFooterAnimator;
+
+    public TaskFooterView(Context context) {
+        this(context, null);
+    }
+
+    public TaskFooterView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
+        mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
+        setFooterHeight(getFooterHeight());
+    }
+
+    /** Sets the callbacks for when the footer height changes. */
+    void setCallbacks(TaskFooterViewCallbacks cb) {
+        mCb = cb;
+        mCb.onTaskFooterHeightChanged(mFooterHeight, mMaxFooterHeight);
+    }
+
+    /** Sets the footer height. */
+    public void setFooterHeight(int footerHeight) {
+        if (footerHeight != mFooterHeight) {
+            mFooterHeight = footerHeight;
+            mCb.onTaskFooterHeightChanged(footerHeight, mMaxFooterHeight);
+        }
+    }
+
+    /** Gets the footer height. */
+    public int getFooterHeight() {
+        return mFooterHeight;
+    }
+
+    /** Animates the footer into and out of view. */
+    void animateFooterVisibility(final boolean visible, int duration) {
+        // Return early if there is no footer
+        if (mMaxFooterHeight <= 0) return;
+        // Return early if we are already in the final state
+        if (!visible && getVisibility() != View.VISIBLE) return;
+        if (visible && getVisibility() == View.VISIBLE) return;
+
+        // Cancel the previous animation
+        if (mFooterAnimator != null) {
+            mFooterAnimator.removeAllListeners();
+            mFooterAnimator.cancel();
+        }
+        int finalHeight = visible ? mMaxFooterHeight : 0;
+        if (duration > 0) {
+            // Make the view visible for the animation
+            if (visible && getVisibility() != View.VISIBLE) {
+                setVisibility(View.VISIBLE);
+            }
+            mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", finalHeight);
+            mFooterAnimator.setDuration(duration);
+            mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+            mFooterAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (!visible) {
+                        setVisibility(View.INVISIBLE);
+                    }
+                }
+            });
+            mFooterAnimator.start();
+        } else {
+            setFooterHeight(finalHeight);
+            setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
+}
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 8b86258..d84a40f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -23,7 +23,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -73,6 +72,8 @@
     ViewPool<TaskView, Task> mViewPool;
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
     DozeTrigger mUIDozeTrigger;
+    DebugOverlayView mDebugOverlay;
+    Rect mTaskStackBounds = new Rect();
 
     // The virtual stack scroll that we use for the card layout
     int mStackScroll;
@@ -94,8 +95,12 @@
     int[] mTmpVisibleRange = new int[2];
     Rect mTmpRect = new Rect();
     Rect mTmpRect2 = new Rect();
+    TaskViewTransform mTmpTransform = new TaskViewTransform();
+    HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
     LayoutInflater mInflater;
 
+    // A convenience runnable to return all views to the pool
+    // XXX: After this is set, we should mark this task stack view as disabled and check that in synchronize model
     Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
         @Override
         public void run() {
@@ -162,6 +167,11 @@
         mCb = cb;
     }
 
+    /** Sets the debug overlay */
+    public void setDebugOverlay(DebugOverlayView overlay) {
+        mDebugOverlay = overlay;
+    }
+
     /** Requests that the views be synchronized with the model */
     void requestSynchronizeStackViewsWithModel() {
         requestSynchronizeStackViewsWithModel(0);
@@ -179,19 +189,8 @@
         }
     }
 
-    /** 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) {
+    public TaskView getChildViewForTask(Task t) {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             TaskView tv = (TaskView) getChildAt(i);
@@ -210,19 +209,23 @@
     /**
      * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
      */
-    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
+    private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
                                        ArrayList<Task> tasks,
                                        int stackScroll,
                                        int[] visibleRangeOut,
                                        boolean boundTranslationsToRect) {
-        // XXX: We should be intelligent about where to look for the visible stack range using the
+        // XXX: We should be intelligent about wheee to look for the visible stack range using the
         //      current stack scroll.
+        // XXX: We should log extra cases like the ones below where we don't expect to hit very often
+        // XXX: Print out approximately how many indices we have to go through to find the first visible transform
 
         int taskTransformCount = taskTransforms.size();
         int taskCount = tasks.size();
         int frontMostVisibleIndex = -1;
         int backMostVisibleIndex = -1;
 
+
+
         // We can reuse the task transforms where possible to reduce object allocation
         if (taskTransformCount < taskCount) {
             // If there are less transforms than tasks, then add as many transforms as necessary
@@ -256,13 +259,14 @@
 
             if (boundTranslationsToRect) {
                 transform.translationY = Math.min(transform.translationY,
-                        mStackAlgorithm.mRect.bottom);
+                        mStackAlgorithm.mViewRect.bottom);
             }
         }
         if (visibleRangeOut != null) {
             visibleRangeOut[0] = frontMostVisibleIndex;
             visibleRangeOut[1] = backMostVisibleIndex;
         }
+        return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
     }
 
     /**
@@ -280,34 +284,33 @@
     }
 
     /** Synchronizes the views with the model */
-    void synchronizeStackViewsWithModel() {
+    boolean synchronizeStackViewsWithModel() {
         if (mStackViewsDirty) {
             // Get all the task transforms
             ArrayList<Task> tasks = mStack.getTasks();
             int stackScroll = getStackScroll();
             int[] visibleRange = mTmpVisibleRange;
-            updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
-            TaskViewTransform tmpTransform = new TaskViewTransform();
+            boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
 
             // Return all the invisible children to the pool
-            HashMap<Task, TaskView> taskChildViewMap = getTaskChildViewMap();
+            mTmpTaskViewMap.clear();
             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 < visibleRange[1] || taskIndex > visibleRange[0]) {
-                    taskChildViewMap.remove(task);
+                if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
+                    mTmpTaskViewMap.put(task, tv);
+                } else {
                     mViewPool.returnViewToPool(tv);
                 }
             }
 
             // 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);
+                TaskView tv = mTmpTaskViewMap.get(task);
                 int taskIndex = mStack.indexOfTask(task);
 
                 if (tv == null) {
@@ -316,23 +319,26 @@
                         // For items in the list, put them in start animating them from the
                         // approriate ends of the list where they are expected to appear
                         if (transform.t < 0) {
-                            tmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, tmpTransform);
+                            mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, mTmpTransform);
                         } else {
-                            tmpTransform = mStackAlgorithm.getStackTransform(tasks.get(Math.min(tasks.size() - 1, visibleRange[0] + 1)),
-                                    stackScroll, tmpTransform);
+                            mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(Math.min(tasks.size() - 1, visibleRange[0] + 1)),
+                                    stackScroll, mTmpTransform);
                         }
-                        tv.updateViewPropertiesToTaskTransform(tmpTransform, 0);
+                        tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
                     }
                 }
 
-                // Update and animate the task into place
+                // Animate the task into place
                 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
                         mStackViewsAnimationDuration);
             }
 
+            // Reset the request-synchronize params
             mStackViewsAnimationDuration = 0;
             mStackViewsDirty = false;
+            return true;
         }
+        return false;
     }
 
     /** Updates the clip for each of the task views. */
@@ -368,13 +374,13 @@
                         clipBottom = (mTmpRect.bottom - scaledMaxFooterHeight - mTmpRect2.top);
                     }
                 }
-                tv.setClipFromBottom(clipBottom);
+                tv.getViewBounds().setClipBottom(clipBottom);
             }
-        }
-        if (getChildCount() > 0) {
-            // The front most task should never be clipped
-            TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
-            tv.setClipFromBottom(0);
+            if (getChildCount() > 0) {
+                // The front most task should never be clipped
+                TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
+                tv.getViewBounds().setClipBottom(0);
+            }
         }
     }
 
@@ -384,18 +390,24 @@
             int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
                 TaskView tv = (TaskView) getChildAt(i);
-                tv.setClipFromBottom(0);
+                tv.getViewBounds().setClipBottom(0);
             }
         }
         mEnableStackClipping = stackClippingEnabled;
     }
 
+    /** The stack insets to apply to the stack contents */
+    public void setStackInsetRect(Rect r) {
+        mTaskStackBounds.set(r);
+    }
+
     /** Sets the current stack scroll */
     public void setStackScroll(int value) {
         mStackScroll = value;
         mUIDozeTrigger.poke();
         requestSynchronizeStackViewsWithModel();
     }
+
     /** Sets the current stack scroll without synchronizing the stack view with the model */
     public void setStackScrollRaw(int value) {
         mStackScroll = value;
@@ -408,7 +420,7 @@
     /** Computes the initial stack scroll for the stack. */
     int getInitialStackScroll() {
         if (mStack.getTaskCount() > 2) {
-            return mMaxScroll - (int) (mStackAlgorithm.mTaskRect.height() * (3f/4f));
+            return Math.max(mMinScroll, mMaxScroll - (int) (mStackAlgorithm.mTaskRect.height() * (3f/4f)));
         }
         return mMaxScroll;
     }
@@ -521,7 +533,7 @@
 
     /** Returns whether the specified scroll is out of bounds */
     boolean isScrollOutOfBounds() {
-        return getScrollAmountOutOfBounds(getStackScroll()) != 0;
+        return getScrollAmountOutOfBounds(mStackScroll) != 0;
     }
 
     /** Updates the min and max virtual scroll bounds */
@@ -639,24 +651,24 @@
 
     @Override
     public void dispatchDraw(Canvas canvas) {
-        synchronizeStackViewsWithModel();
-        clipTaskViews();
+        if (synchronizeStackViewsWithModel()) {
+            clipTaskViews();
+        }
         super.dispatchDraw(canvas);
     }
 
     /** Computes the stack and task rects */
-    public void computeRects(int width, int height, int insetLeft, int insetBottom) {
+    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
         // Compute the rects in the stack algorithm
-        mStackAlgorithm.computeRects(mStack.getTasks(), width, height, insetLeft, insetBottom);
+        mStackAlgorithm.computeRects(mStack.getTasks(), windowWidth, windowHeight, taskStackBounds);
 
         // Update the scroll bounds
         updateMinMaxScroll(false);
     }
 
     /**
-     * This is called with the size of the space not including the top or right insets, or the
-     * search bar height in portrait (but including the search bar width in landscape, since we want
-     * to draw under it.
+     * This is called with the full window width and height to allow stack view children to 
+     * perform the full screen transition down.
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@@ -664,25 +676,43 @@
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
         // Compute our stack/task rects
-        Rect taskStackBounds = new Rect();
-        mConfig.getTaskStackBounds(width, height, taskStackBounds);
-        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
+        Rect taskStackBounds = new Rect(mTaskStackBounds);
+        taskStackBounds.bottom -= mConfig.systemInsets.bottom;
+        computeRects(width, height, taskStackBounds);
 
         // If this is the first layout, then scroll to the front of the stack and synchronize the
-        // stack views immediately
+        // stack views immediately to load all the views
         if (mAwaitingFirstLayout) {
             setStackScrollToInitialState();
             requestSynchronizeStackViewsWithModel();
             synchronizeStackViewsWithModel();
+
+            // Find the first task and mark it as full screen
+            if (mConfig.launchedFromAppWithScreenshot) {
+                int childCount = getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    TaskView tv = (TaskView) getChildAt(i);
+                    if (tv.getTask().isLaunchTarget) {
+                        tv.setIsFullScreen(true);
+                        break;
+                    }
+                }
+            }
         }
 
-        // Measure each of the children
+        // Measure each of the TaskViews
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
-            TaskView t = (TaskView) getChildAt(i);
-            t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
+            TaskView tv = (TaskView) getChildAt(i);
+            if (tv.isFullScreenView()) {
+                tv.measure(widthMeasureSpec, heightMeasureSpec);
+            } else {
+                tv.measure(
+                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(),
+                            MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
-                            t.getMaxFooterHeight(), MeasureSpec.EXACTLY));
+                            tv.getMaxFooterHeight(), MeasureSpec.EXACTLY));
+            }
         }
 
         setMeasuredDimension(width, height);
@@ -698,54 +728,45 @@
         // Layout each of the children
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
-            TaskView t = (TaskView) getChildAt(i);
-            t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
-                    mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
-                    mStackAlgorithm.mTaskRect.height() + t.getMaxFooterHeight());
+            TaskView tv = (TaskView) getChildAt(i);
+            if (tv.isFullScreenView()) {
+                tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight());
+            } else {
+                tv.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mTaskRect.top,
+                        mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mTaskRect.bottom +
+                                tv.getMaxFooterHeight());
+            }
         }
 
         if (mAwaitingFirstLayout) {
-            // Mark that we have completely the first layout
             mAwaitingFirstLayout = false;
+            onFirstLayout();
+        }
+    }
 
-            // Find the target task with the specified id
-            ArrayList<Task> tasks = mStack.getTasks();
-            Task targetTask = null;
-            int targetTaskId = mConfig.launchedToTaskId;
-            if (targetTaskId != -1) {
-                int taskCount = tasks.size();
-                for (int i = 0; i < taskCount; i++) {
-                    Task t = tasks.get(i);
-                    if (t.key.id == targetTaskId) {
-                        targetTask = t;
-                        break;
-                    }
-                }
-            }
+    /** Handler for the first layout. */
+    void onFirstLayout() {
+        // Prepare the first view for its enter animation
+        int offscreenY = mStackAlgorithm.mViewRect.bottom -
+                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            TaskView tv = (TaskView) getChildAt(i);
+            tv.prepareEnterRecentsAnimation(tv.getTask().isLaunchTarget, offscreenY);
+        }
 
-            // Prepare the first view for its enter animation
-            int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
-            int offscreenY = mStackAlgorithm.mRect.bottom -
-                    (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
-            for (int i = childCount - 1; i >= 0; i--) {
-                TaskView tv = (TaskView) getChildAt(i);
-                tv.prepareEnterRecentsAnimation(tv.getTask() == targetTask, offsetTopAlign,
-                        offscreenY);
-            }
+        // If the enter animation started already and we haven't completed a layout yet, do the
+        // enter animation now
+        if (mStartEnterAnimationRequestedAfterLayout) {
+            startEnterRecentsAnimation(mStartEnterAnimationContext);
+            mStartEnterAnimationRequestedAfterLayout = false;
+            mStartEnterAnimationContext = null;
+        }
 
-            // If the enter animation started already and we haven't completed a layout yet, do the
-            // enter animation now
-            if (mStartEnterAnimationRequestedAfterLayout) {
-                startEnterRecentsAnimation(mStartEnterAnimationContext);
-                mStartEnterAnimationRequestedAfterLayout = false;
-                mStartEnterAnimationContext = null;
-            }
-
-            // Update the focused task index to be the next item to the top task
-            if (mConfig.launchedWithAltTab) {
-                // When alt-tabbing, we focus the next previous task
-                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
-            }
+        // Update the focused task index to be the next item to the top task
+        if (mConfig.launchedWithAltTab) {
+            // When alt-tabbing, we focus the next previous task
+            focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
         }
     }
 
@@ -759,30 +780,6 @@
         }
 
         if (mStack.getTaskCount() > 0) {
-            // Find the target task with the specified id
-            ArrayList<Task> tasks = mStack.getTasks();
-            Task targetTask = null;
-            int targetTaskId = mConfig.launchedToTaskId;
-            if (targetTaskId != -1) {
-                int taskCount = tasks.size();
-                for (int i = 0; i < taskCount; i++) {
-                    Task t = tasks.get(i);
-                    if (t.key.id == targetTaskId) {
-                        targetTask = t;
-                        break;
-                    }
-                }
-            }
-
-            // Find the transform for the target task
-            if (targetTask != null) {
-                ctx.targetTaskTransform = new TaskViewTransform();
-                mStackAlgorithm.getStackTransform(targetTask, getStackScroll(), ctx.targetTaskTransform);
-                Rect taskStackBounds = new Rect();
-                mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
-                ctx.targetTaskTransform.rect.offset(taskStackBounds.left, taskStackBounds.top);
-            }
-
             // Animate all the task views into view
             int childCount = getChildCount();
             for (int i = childCount - 1; i >= 0; i--) {
@@ -791,7 +788,7 @@
                 ctx.currentTaskTransform = new TaskViewTransform();
                 ctx.currentStackViewIndex = i;
                 ctx.currentStackViewCount = childCount;
-                ctx.isCurrentTaskLaunchTarget = (task == targetTask);
+                ctx.currentTaskRect = mStackAlgorithm.mTaskRect;
                 mStackAlgorithm.getStackTransform(task, getStackScroll(), ctx.currentTaskTransform);
                 tv.startEnterRecentsAnimation(ctx);
             }
@@ -802,6 +799,11 @@
                 public void run() {
                     // Start dozing
                     mUIDozeTrigger.startDozing();
+                    // Request an update of the task views after the animation in to 
+                    // relayout the fullscreen view back to its normal size
+                    if (mConfig.launchedFromAppWithScreenshot) {
+                        requestSynchronizeStackViewsWithModel();
+                    }
                 }
             });
         }
@@ -810,8 +812,8 @@
     /** Requests this task stacks to start it's exit-recents animation. */
     public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
         // Animate all the task views into view
-        ctx.offscreenTranslationY = mStackAlgorithm.mRect.bottom -
-                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
+        ctx.offscreenTranslationY = mStackAlgorithm.mViewRect.bottom -
+                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             TaskView tv = (TaskView) getChildAt(i);
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 65407a6..3c89cd7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -17,7 +17,6 @@
 package com.android.systemui.recents.views;
 
 import android.graphics.Rect;
-import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
@@ -30,14 +29,14 @@
 
     // These are all going to change
     static final float StackOverlapPct = 0.65f; // The overlap height relative to the task height
-    static final float StackPeekHeightPct = 0.1f; // The height of the peek space relative to the stack height
+    static final float StackPeekHeightPct = 0.075f; // The height of the peek space relative to the stack height
     static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
     static final int StackPeekNumCards = 3; // The number of cards we see in the peek space
 
     RecentsConfiguration mConfig;
 
     // The various rects that define the stack view
-    Rect mRect = new Rect();
+    Rect mViewRect = new Rect();
     Rect mStackRect = new Rect();
     Rect mStackRectSansPeek = new Rect();
     Rect mTaskRect = new Rect();
@@ -53,29 +52,21 @@
     }
 
     /** Computes the stack and task rects */
-    public void computeRects(ArrayList<Task> tasks, int width, int height, int insetLeft, int insetBottom) {
+    public void computeRects(ArrayList<Task> tasks, int windowWidth, int windowHeight,
+                             Rect taskStackBounds) {
         // Note: We let the stack view be the full height because we want the cards to go under the
         //       navigation bar if possible.  However, the stack rects which we use to calculate
         //       max scroll, etc. need to take the nav bar into account
 
         // Compute the stack rects
-        mRect.set(0, 0, width, height);
-        mStackRect.set(mRect);
-        mStackRect.left += insetLeft;
-        mStackRect.bottom -= insetBottom;
+        mViewRect.set(0, 0, windowWidth, windowHeight);
+        mStackRect.set(taskStackBounds);
 
         int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
         int heightPadding = mConfig.taskStackTopPaddingPx;
-        if (Constants.DebugFlags.App.EnableSearchLayout) {
-            mStackRect.top += heightPadding;
-            mStackRect.left += widthPadding;
-            mStackRect.right -= widthPadding;
-            mStackRect.bottom -= heightPadding;
-        } else {
-            mStackRect.inset(widthPadding, heightPadding);
-        }
+        mStackRect.inset(widthPadding, heightPadding);
         mStackRectSansPeek.set(mStackRect);
-        mStackRectSansPeek.top += StackPeekHeightPct * mStackRect.height();
+        mStackRectSansPeek.top += StackPeekHeightPct * windowHeight;
 
         // Compute the task rect
         int size = mStackRect.width();
@@ -91,7 +82,7 @@
         // Compute the min and max scroll values
         int numTasks = Math.max(1, tasks.size());
         int taskHeight = mTaskRect.height();
-        int stackHeight = mStackRectSansPeek.height();
+        int stackHeight = mStackRect.height();
 
         if (numTasks <= 1) {
             // If there is only one task, then center the task in the stack rect (sans peek)
@@ -156,7 +147,7 @@
         } else {
             transformOut.rect.offset(0, transformOut.translationY);
             Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-            transformOut.visible = Rect.intersects(mRect, transformOut.rect);
+            transformOut.visible = Rect.intersects(mViewRect, transformOut.rect);
         }
         transformOut.t = t;
         return transformOut;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
index 08a25f1..1116d51 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.views;
 
 import android.content.Context;
-import android.graphics.Canvas;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
@@ -27,11 +27,8 @@
 /** The task thumbnail view */
 public class TaskThumbnailView extends FixedSizeImageView {
 
-    Task mTask;
-
     // Task bar clipping
-    Rect mClipRect;
-    boolean mClipTaskBar = true;
+    Rect mClipRect = new Rect();
 
     public TaskThumbnailView(Context context) {
         this(context, null);
@@ -50,40 +47,51 @@
         setScaleType(ScaleType.FIT_XY);
     }
 
-    @Override
-    public void draw(Canvas canvas) {
-        if (mClipTaskBar && (mClipRect != null)) {
-            int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
-            canvas.clipRect(mClipRect);
-            super.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        } else {
-            super.draw(canvas);
-        }
+    /** Updates the clip rect based on the given task bar. */
+    void enableTaskBarClip(View taskBar) {
+        int top = (int) Math.max(0, taskBar.getTranslationY() +
+                taskBar.getMeasuredHeight() - 1);
+        mClipRect.set(0, top, getMeasuredWidth(), getMeasuredHeight());
+        setClipBounds(mClipRect);
     }
 
-    /** Updates the clip rect based on the given task bar. */
-    void updateTaskBarClip(View taskBar) {
-        // If mClipTaskBar is unset first, then we don't bother setting mTaskBar
-        if (mClipTaskBar) {
-            int top = (int) Math.max(0, taskBar.getTranslationY() +
-                    taskBar.getMeasuredHeight() - 1);
-            mClipRect = new Rect(0, top, getMeasuredWidth(), getMeasuredHeight());
-            invalidate(0, 0, taskBar.getMeasuredWidth(), taskBar.getMeasuredHeight() + 1);
-        }
+    /** Convenience method to enable task bar clipping as a runnable. */
+    Runnable enableTaskBarClipAsRunnable(final View taskBar) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                enableTaskBarClip(taskBar);
+            }
+        };
     }
 
     /** Disables the task bar clipping. */
-    void disableClipTaskBarView() {
-        mClipTaskBar = false;
-        if (mClipRect != null) {
-            invalidate(0, 0, mClipRect.width(), mClipRect.top);
+    Runnable disableTaskBarClipAsRunnable() {
+        return new Runnable() {
+            @Override
+            public void run() {
+                mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+                setClipBounds(mClipRect);
+            }
+        };
+    }
+
+    /** Binds the thumbnail view to the screenshot. */
+    boolean bindToScreenshot(Bitmap ss) {
+        if (ss != null) {
+            setImageBitmap(ss);
+            return true;
         }
+        return false;
+    }
+
+    /** Unbinds the thumbnail view from the screenshot. */
+    void unbindFromScreenshot() {
+        setImageBitmap(null);
     }
 
     /** Binds the thumbnail view to the task */
     void rebindToTask(Task t) {
-        mTask = t;
         if (t.thumbnail != null) {
             setImageBitmap(t.thumbnail);
         }
@@ -91,7 +99,6 @@
 
     /** Unbinds the thumbnail view from the task */
     void unbindFromTask() {
-        mTask = null;
         setImageDrawable(null);
     }
 }
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 4757c5f..d1b33f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,24 +22,23 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.view.ViewPropertyAnimator;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.AlternateRecentsComponent;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
 
+// XXX: In debug mode, we should override invalidate() and check the layout type (do this in TaskStackView as well)
 
 /* A task view */
-public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener,
-        View.OnLongClickListener {
+public class TaskView extends FrameLayout implements Task.TaskCallbacks,
+        TaskFooterView.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
         public void onTaskViewAppIconClicked(TaskView tv);
@@ -51,10 +50,6 @@
 
     RecentsConfiguration mConfig;
 
-    int mFooterHeight;
-    int mMaxFooterHeight;
-    ObjectAnimator mFooterAnimator;
-
     int mDim;
     int mMaxDim;
     AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -62,14 +57,15 @@
     Task mTask;
     boolean mTaskDataLoaded;
     boolean mIsFocused;
+    boolean mIsFullScreenView;
     boolean mIsStub;
     boolean mClipViewInStack;
-    int mClipFromBottom;
+    AnimateableViewBounds mViewBounds;
     Paint mLayerPaint = new Paint();
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
-    View mLockToAppButtonView;
+    TaskFooterView mFooterView;
     TaskViewCallbacks mCb;
 
     // Optimizations
@@ -80,18 +76,6 @@
                     updateDimOverlayFromScale();
                 }
             };
-    Runnable mEnableThumbnailClip = new Runnable() {
-        @Override
-        public void run() {
-            mThumbnailView.updateTaskBarClip(mBarView);
-        }
-    };
-    Runnable mDisableThumbnailClip = new Runnable() {
-        @Override
-        public void run() {
-            mThumbnailView.disableClipTaskBarView();
-        }
-    };
 
 
     public TaskView(Context context) {
@@ -109,55 +93,12 @@
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
-        mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
-        setWillNotDraw(false);
-        setClipToOutline(true);
-        setDim(getDim());
-        setFooterHeight(getFooterHeight());
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                // The current height is measured with the footer, so account for the footer height
-                // and the current clip (in the stack)
-                int height = getMeasuredHeight() - mClipFromBottom - mMaxFooterHeight + mFooterHeight;
-                outline.setRoundRect(0, 0, getWidth(), height,
-                        mConfig.taskViewRoundedCornerRadiusPx);
-            }
-        });
-    }
-
-    @Override
-    protected void onFinishInflate() {
         mMaxDim = mConfig.taskStackMaxDim;
-
-        // By default, all views are clipped to other views in their stack
         mClipViewInStack = true;
-
-        // Bind the views
-        mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
-        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
-        mLockToAppButtonView = findViewById(R.id.lock_to_app);
-
-        if (mTaskDataLoaded) {
-            onTaskDataLoaded();
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        // Measure the bar view, thumbnail, and lock-to-app buttons
-        mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
-        mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
-                        MeasureSpec.EXACTLY));
-        // Measure the thumbnail height to be the same as the width
-        mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
-        setMeasuredDimension(width, height);
+        mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);
+        setWillNotDraw(false);
+        setDim(getDim());
+        setOutlineProvider(mViewBounds);
     }
 
     /** Set callback */
@@ -170,74 +111,67 @@
         return mTask;
     }
 
+    /** Returns the view bounds. */
+    AnimateableViewBounds getViewBounds() {
+        return mViewBounds;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        // Bind the views
+        mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
+        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+        mFooterView = (TaskFooterView) findViewById(R.id.lock_to_app);
+        mFooterView.setCallbacks(this);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+
+        // Measure the bar view, thumbnail, and footer
+        mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
+        mFooterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
+                        MeasureSpec.EXACTLY));
+        if (mIsFullScreenView) {
+            // Measure the thumbnail height to be the full dimensions
+            mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+        } else {
+            // Measure the thumbnail to be square
+            mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
+        }
+        setMeasuredDimension(width, height);
+    }
+
     /** Synchronizes this view's properties with the task's transform */
     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
         // Update the bar view
         mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
 
-        // Check to see if any properties have changed, and update the task view
-        if (duration > 0) {
-            ViewPropertyAnimator anim = animate();
-            boolean useLayers = false;
-
-            // Animate to the final state
-            if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
-                anim.translationY(toTransform.translationY);
-            }
-            if (Constants.DebugFlags.App.EnableShadows &&
-                    toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
-                anim.translationZ(toTransform.translationZ);
-            }
-            if (toTransform.hasScaleChangedFrom(getScaleX())) {
-                anim.scaleX(toTransform.scale)
-                    .scaleY(toTransform.scale)
-                    .setUpdateListener(mUpdateDimListener);
-                useLayers = true;
-            }
-            if (toTransform.hasAlphaChangedFrom(getAlpha())) {
-                // Use layers if we animate alpha
-                anim.alpha(toTransform.alpha);
-                useLayers = true;
-            }
-            if (useLayers) {
-                anim.withLayer();
-            }
-            anim.setStartDelay(toTransform.startDelay)
-                .setDuration(duration)
-                .setInterpolator(mConfig.fastOutSlowInInterpolator)
-                .start();
-        } else {
-            // Set the changed properties
-            if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
-                setTranslationY(toTransform.translationY);
-            }
+        // If we are a full screen view, then only update the Z to keep it in order
+        // XXX: Also update/animate the dim as well
+        if (mIsFullScreenView) {
             if (Constants.DebugFlags.App.EnableShadows &&
                     toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
                 setTranslationZ(toTransform.translationZ);
             }
-            if (toTransform.hasScaleChangedFrom(getScaleX())) {
-                setScaleX(toTransform.scale);
-                setScaleY(toTransform.scale);
-                updateDimOverlayFromScale();
-            }
-            if (toTransform.hasAlphaChangedFrom(getAlpha())) {
-                setAlpha(toTransform.alpha);
-            }
+            return;
         }
+
+        // Apply the transform
+        toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator,
+                mUpdateDimListener);
     }
 
     /** Resets this view's properties */
     void resetViewProperties() {
-        setTranslationX(0f);
-        setTranslationY(0f);
-        if (Constants.DebugFlags.App.EnableShadows) {
-            setTranslationZ(0f);
-        }
-        setScaleX(1f);
-        setScaleY(1f);
-        setAlpha(1f);
         setDim(0);
-        invalidate();
+        TaskViewTransform.reset(this);
     }
 
     /**
@@ -262,20 +196,13 @@
 
     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
      * first layout because the actual animation into recents may take a long time. */
-    public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offsetY,
-                                             int offscreenY) {
+    public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offscreenY) {
         if (mConfig.launchedFromAppWithScreenshot) {
             if (isTaskViewLaunchTargetTask) {
-                // Hide the task view as we are going to animate the full screenshot into view
-                // and then replace it with this view once we are done
-                setVisibility(View.INVISIBLE);
                 // Also hide the front most task bar view so we can animate it in
                 mBarView.prepareEnterRecentsAnimation();
             } else {
-                // Top align the task views
-                setTranslationY(offsetY);
-                setScaleX(1f);
-                setScaleY(1f);
+                // Don't do anything for the side views
             }
 
         } else if (mConfig.launchedFromAppWithThumbnail) {
@@ -300,49 +227,57 @@
     /** Animates this task view as it enters recents */
     public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
         TaskViewTransform transform = ctx.currentTaskTransform;
+        Rect taskRect = ctx.currentTaskRect;
 
         if (mConfig.launchedFromAppWithScreenshot) {
-            if (ctx.isCurrentTaskLaunchTarget) {
-                // Animate the full screenshot down first, before swapping with this task view
-                ctx.fullScreenshotView.animateOnEnterRecents(ctx, new Runnable() {
-                    @Override
-                    public void run() {
-                        // Animate the task bar of the first task view
-                        mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
-                        setVisibility(View.VISIBLE);
-                        // Animate the footer into view
-                        animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
-                        // Decrement the post animation trigger
-                        ctx.postAnimationTrigger.decrement();
-                    }
-                });
-            } else {
-                // Animate the tasks down behind the full screenshot
-                animate()
-                        .scaleX(transform.scale)
-                        .scaleY(transform.scale)
-                        .translationY(transform.translationY)
-                        .setStartDelay(0)
-                        .setUpdateListener(null)
-                        .setInterpolator(mConfig.linearOutSlowInInterpolator)
-                        .setDuration(475)
-                        .withLayer()
+            if (mTask.isLaunchTarget) {
+                // XXX: We would have to animate the trasnlationY of the task view bar along with the clip and
+                // reset it at the bottom
+
+                // XXX: This should actually be the inset on the current app...
+                mViewBounds.animateClipTop(taskRect.top, mConfig.taskViewEnterFromHomeDuration * 5);
+                mViewBounds.animateClipBottom(getMeasuredHeight() - taskRect.bottom, mConfig.taskViewEnterFromHomeDuration * 5);
+
+                animate().scaleX(((float) taskRect.width() / getMeasuredWidth()) * transform.scale)
+                        .scaleY(((float) taskRect.width() / getMeasuredWidth()) * transform.scale)
+                        .translationY(taskRect.top + transform.translationY)
+                        .setDuration(mConfig.taskViewEnterFromHomeDuration * 5)
                         .withEndAction(new Runnable() {
                             @Override
                             public void run() {
-                                mEnableThumbnailClip.run();
+                                // Animate the task bar of the first task view
+                                mBarView.startEnterRecentsAnimation(0, mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
+                                // Animate the footer into view (if it is the front most task)
+                                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
                                 // Decrement the post animation trigger
                                 ctx.postAnimationTrigger.decrement();
+
+                                // XXX Request layout and only start hte next animation after the next
+                                // layout
+
+                                setIsFullScreen(false);
+                                mThumbnailView.unbindFromScreenshot();
+
+                                // Recycle the full screen screenshot
+                                AlternateRecentsComponent.consumeLastScreenshot();
                             }
                         })
+                        .withLayer()
                         .start();
+            } else {
+                // Otherwise, just enable the thumbnail clip
+                mThumbnailView.enableTaskBarClip(mBarView);
+
+                // Animate the footer into view
+                animateFooterVisibility(true, 0);
             }
             ctx.postAnimationTrigger.increment();
 
         } else if (mConfig.launchedFromAppWithThumbnail) {
-            if (ctx.isCurrentTaskLaunchTarget) {
+            if (mTask.isLaunchTarget) {
                 // Animate the task bar of the first task view
-                mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip);
+                mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
+                        mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
 
                 // Animate the dim into view as well
                 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale());
@@ -360,10 +295,9 @@
                 ctx.postAnimationTrigger.increment();
 
                 // Animate the footer into view
-                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration
-                );
+                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
             } else {
-                mEnableThumbnailClip.run();
+                mThumbnailView.enableTaskBarClip(mBarView);
             }
 
         } else if (mConfig.launchedFromHome) {
@@ -386,7 +320,7 @@
                     .withEndAction(new Runnable() {
                         @Override
                         public void run() {
-                            mEnableThumbnailClip.run();
+                            mThumbnailView.enableTaskBarClip(mBarView);
                             // Decrement the post animation trigger
                             ctx.postAnimationTrigger.decrement();
                         }
@@ -395,11 +329,11 @@
             ctx.postAnimationTrigger.increment();
 
             // Animate the footer into view
-            animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration
-            );
+            animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
+
         } else {
             // Otherwise, just enable the thumbnail clip
-            mEnableThumbnailClip.run();
+            mThumbnailView.enableTaskBarClip(mBarView);
 
             // Animate the footer into view
             animateFooterVisibility(true, 0);
@@ -424,7 +358,7 @@
     public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) {
         if (isLaunchingTask) {
             // Disable the thumbnail clip and animate the bar out
-            mBarView.startLaunchTaskAnimation(mDisableThumbnailClip, r);
+            mBarView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
 
             // Animate the dim
             if (mDim > 0) {
@@ -478,32 +412,23 @@
         mBarView.setNoUserInteractionState();
     }
 
-    /** Enable the hw layers on this task view */
-    void enableHwLayers() {
-        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
-        mBarView.enableHwLayers();
-        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
+    /** Sets whether this task view is full screen or not. */
+    void setIsFullScreen(boolean isFullscreen) {
+        mIsFullScreenView = isFullscreen;
+        mBarView.setIsFullscreen(isFullscreen);
+        if (isFullscreen) {
+            // If we are full screen, then disable the bottom outline clip for the footer
+            mViewBounds.setOutlineClipBottom(0);
+        }
     }
 
-    /** Disable the hw layers on this task view */
-    void disableHwLayers() {
-        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
-        mBarView.disableHwLayers();
-        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
+    /** Returns whether this task view should currently be drawn as a full screen view. */
+    boolean isFullScreenView() {
+        return mIsFullScreenView;
     }
 
     /** 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;
     }
 
@@ -512,7 +437,7 @@
      * view.
      */
     boolean shouldClipViewInStack() {
-        return mClipViewInStack && (getVisibility() == View.VISIBLE);
+        return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE);
     }
 
     /** Sets whether this view should be clipped, or clipped against. */
@@ -523,75 +448,19 @@
         }
     }
 
-    void setClipFromBottom(int clipFromBottom) {
-        clipFromBottom = Math.max(0, Math.min(getMeasuredHeight(), clipFromBottom));
-        if (mClipFromBottom != clipFromBottom) {
-            mClipFromBottom = clipFromBottom;
-            invalidateOutline();
-        }
-    }
-
-    /** Sets the footer height. */
-    public void setFooterHeight(int footerHeight) {
-        if (footerHeight != mFooterHeight) {
-            mFooterHeight = footerHeight;
-            invalidateOutline();
-            invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
-                    getMeasuredHeight());
-        }
-    }
-
-    /** Gets the footer height. */
-    public int getFooterHeight() {
-        return mFooterHeight;
-    }
-
     /** Gets the max footer height. */
     public int getMaxFooterHeight() {
-        return mMaxFooterHeight;
+        return mFooterView.mMaxFooterHeight;
     }
 
     /** Animates the footer into and out of view. */
-    public void animateFooterVisibility(boolean visible, int duration) {
-        if (!mTask.lockToThisTask) {
-            if (mLockToAppButtonView.getVisibility() == View.VISIBLE) {
-                mLockToAppButtonView.setVisibility(View.INVISIBLE);
-            }
-            return;
-        }
-        if (mMaxFooterHeight <= 0) return;
-
-        if (mFooterAnimator != null) {
-            mFooterAnimator.removeAllListeners();
-            mFooterAnimator.cancel();
-        }
-        int height = visible ? mMaxFooterHeight : 0;
-        if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
-            if (duration > 0) {
-                setFooterHeight(0);
-            } else {
-                setFooterHeight(mMaxFooterHeight);
-            }
-            mLockToAppButtonView.setVisibility(View.VISIBLE);
-        }
-        if (duration > 0) {
-            mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
-            mFooterAnimator.setDuration(duration);
-            mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
-            if (!visible) {
-                mFooterAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mLockToAppButtonView.setVisibility(View.INVISIBLE);
-                    }
-                });
-            }
-            mFooterAnimator.start();
-        } else {
-            if (!visible) {
-                mLockToAppButtonView.setVisibility(View.INVISIBLE);
-            }
-        }
+    void animateFooterVisibility(boolean visible, int duration) {
+        // Hide the footer if we are a full screen view
+        if (mIsFullScreenView) return;
+        // Hide the footer if the current task can not be locked to
+        if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return;
+        // Otherwise, animate the visibility
+        mFooterView.animateFooterVisibility(visible, duration);
     }
 
     /** Returns the current dim. */
@@ -619,6 +488,8 @@
         setDim(getDimOverlayFromScale());
     }
 
+    /**** View drawing ****/
+
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
@@ -631,13 +502,29 @@
 
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        if (mIsStub && (child == mThumbnailView)) {
+        if (mIsStub && (child != mBarView)) {
             // Skip the thumbnail view if we are in stub mode
             return false;
         }
         return super.drawChild(canvas, child, drawingTime);
     }
 
+    /** Enable the hw layers on this task view */
+    void enableHwLayers() {
+        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
+        mBarView.enableHwLayers();
+        mFooterView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
+    }
+
+    /** Disable the hw layers on this task view */
+    void disableHwLayers() {
+        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
+        mBarView.disableHwLayers();
+        mFooterView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
+    }
+
+    /**** View focus state ****/
+
     /**
      * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
      * if the view is not currently visible, or we are in touch state (where we still want to keep
@@ -691,14 +578,18 @@
     public void onTaskDataLoaded() {
         if (mThumbnailView != null && mBarView != null) {
             // Bind each of the views to the new task data
-            mThumbnailView.rebindToTask(mTask);
+            if (mIsFullScreenView) {
+                mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
+            } else {
+                mThumbnailView.rebindToTask(mTask);
+            }
             mBarView.rebindToTask(mTask);
             // Rebind any listeners
             if (Constants.DebugFlags.App.EnableTaskFiltering) {
                 mBarView.mApplicationIcon.setOnClickListener(this);
             }
             mBarView.mDismissButton.setOnClickListener(this);
-            mLockToAppButtonView.setOnClickListener(this);
+            mFooterView.setOnClickListener(this);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 if (mConfig.developerOptionsEnabled) {
                     mBarView.mApplicationIcon.setOnLongClickListener(this);
@@ -720,7 +611,7 @@
                 mBarView.mApplicationIcon.setOnClickListener(null);
             }
             mBarView.mDismissButton.setOnClickListener(null);
-            mLockToAppButtonView.setOnClickListener(null);
+            mFooterView.setOnClickListener(null);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 mBarView.mApplicationIcon.setOnLongClickListener(null);
             }
@@ -733,6 +624,21 @@
         setOnClickListener(enabled ? this : null);
     }
 
+    /**** TaskFooterView.TaskFooterViewCallbacks ****/
+
+    @Override
+    public void onTaskFooterHeightChanged(int height, int maxHeight) {
+        if (mIsFullScreenView) {
+            // Disable the bottom outline clip when fullscreen
+            mViewBounds.setOutlineClipBottom(0);
+        } else {
+            // Update the bottom clip in our outline provider
+            mViewBounds.setOutlineClipBottom(maxHeight - height);
+        }
+    }
+
+    /**** View.OnClickListener Implementation ****/
+
     @Override
     public void onClick(final View v) {
         // We purposely post the handler delayed to allow for the touch feedback to draw
@@ -752,13 +658,15 @@
                     });
                     // Hide the footer
                     tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
-                } else if (v == tv || v == mLockToAppButtonView) {
-                    mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
+                } else if (v == tv || v == mFooterView) {
+                    mCb.onTaskViewClicked(tv, tv.getTask(), (v == mFooterView));
                 }
             }
         }, 125);
     }
 
+    /**** View.OnLongClickListener Implementation ****/
+
     @Override
     public boolean onLongClick(View v) {
         if (v == mBarView.mApplicationIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index b351b03..d583c20 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -16,7 +16,12 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.ValueAnimator;
 import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.Interpolator;
+import com.android.systemui.recents.Constants;
 
 
 /* The transform state for a task view */
@@ -77,6 +82,73 @@
         return (Float.compare(translationZ, v) != 0);
     }
 
+    /** Applies this transform to a view. */
+    public void applyToTaskView(View v, int duration, Interpolator interp,
+                                ValueAnimator.AnimatorUpdateListener scaleUpdateListener) {
+        // Check to see if any properties have changed, and update the task view
+        if (duration > 0) {
+            ViewPropertyAnimator anim = v.animate();
+            boolean useLayers = false;
+
+            // Animate to the final state
+            if (hasTranslationYChangedFrom(v.getTranslationY())) {
+                anim.translationY(translationY);
+            }
+            if (Constants.DebugFlags.App.EnableShadows &&
+                    hasTranslationZChangedFrom(v.getTranslationZ())) {
+                anim.translationZ(translationZ);
+            }
+            if (hasScaleChangedFrom(v.getScaleX())) {
+                anim.scaleX(scale)
+                    .scaleY(scale)
+                    .setUpdateListener(scaleUpdateListener);
+                useLayers = true;
+            }
+            if (hasAlphaChangedFrom(v.getAlpha())) {
+                // Use layers if we animate alpha
+                anim.alpha(alpha);
+                useLayers = true;
+            }
+            if (useLayers) {
+                anim.withLayer();
+            }
+            anim.setStartDelay(startDelay)
+                    .setDuration(duration)
+                    .setInterpolator(interp)
+                    .start();
+        } else {
+            // Set the changed properties
+            if (hasTranslationYChangedFrom(v.getTranslationY())) {
+                v.setTranslationY(translationY);
+            }
+            if (Constants.DebugFlags.App.EnableShadows &&
+                    hasTranslationZChangedFrom(v.getTranslationZ())) {
+                v.setTranslationZ(translationZ);
+            }
+            if (hasScaleChangedFrom(v.getScaleX())) {
+                v.setScaleX(scale);
+                v.setScaleY(scale);
+                scaleUpdateListener.onAnimationUpdate(null);
+            }
+            if (hasAlphaChangedFrom(v.getAlpha())) {
+                v.setAlpha(alpha);
+            }
+        }
+    }
+
+    /** Reset the transform on a view. */
+    public static void reset(View v) {
+        v.setTranslationX(0f);
+        v.setTranslationY(0f);
+        if (Constants.DebugFlags.App.EnableShadows) {
+            v.setTranslationZ(0f);
+        }
+        v.setScaleX(1f);
+        v.setScaleY(1f);
+        v.setAlpha(1f);
+        v.invalidate();
+    }
+
     @Override
     public String toString() {
         return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ +
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 e50a5cf..3705cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents.views;
 
+import android.graphics.Rect;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 
 /* Common code related to view animations */
@@ -23,27 +24,22 @@
 
     /* The animation context for a task view animation into Recents */
     public static class TaskViewEnterContext {
-        // The full screenshot view that we are animating down
-        FullscreenTransitionOverlayView fullScreenshotView;
-        // The transform of the target task view that we are animating into
-        TaskViewTransform targetTaskTransform;
         // A trigger to run some logic when all the animations complete.  This works around the fact
         // that it is difficult to coordinate ViewPropertyAnimators
         ReferenceCountedTrigger postAnimationTrigger;
 
         // These following properties are updated for each task view we start the enter animation on
 
+        // The task rect for the current stack
+        Rect currentTaskRect;
         // The transform of the current task view
         TaskViewTransform currentTaskTransform;
-        // Whether this is the front most task view
-        boolean isCurrentTaskLaunchTarget;
         // The view index of the current task view
         int currentStackViewIndex;
         // The total number of task views
         int currentStackViewCount;
 
-        public TaskViewEnterContext(FullscreenTransitionOverlayView fss, ReferenceCountedTrigger t) {
-            fullScreenshotView = fss;
+        public TaskViewEnterContext(ReferenceCountedTrigger t) {
             postAnimationTrigger = t;
         }
     }
@@ -53,6 +49,7 @@
         // A trigger to run some logic when all the animations complete.  This works around the fact
         // that it is difficult to coordinate ViewPropertyAnimators
         ReferenceCountedTrigger postAnimationTrigger;
+
         // The translationY to apply to a TaskView to move it off the bottom of the task stack
         int offscreenTranslationY;