Exploring transitions to/from Recents.

- refactored hwlayers and change view property animations to use a reference counted trigger
- cleaned up RecentsConfiguration, and move it into classes using it
- moved task bar animations back into TaskBarView
- refactoring enter/exit animations to use an animation context

Change-Id: Ia66b622b094f22145c2fab07c2a9bdfd62344be2
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 65b1f8c..69680c9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2775,6 +2775,13 @@
     /**
      * @hide
      *
+     * Whether Recents is visible or not.
+     */
+    public static final int RECENT_APPS_VISIBLE = 0x00004000;
+
+    /**
+     * @hide
+     *
      * Makes system ui transparent.
      */
     public static final int SYSTEM_UI_TRANSPARENT = 0x00008000;
@@ -2782,7 +2789,7 @@
     /**
      * @hide
      */
-    public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00007FFF;
+    public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00003FFF;
 
     /**
      * These are the system UI flags that can be cleared by events outside
diff --git a/packages/SystemUI/res/anim/recents_from_app_enter.xml b/packages/SystemUI/res/anim/recents_from_app_enter.xml
new file mode 100644
index 0000000..6abe8b3
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_app_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="0"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_from_app_exit.xml b/packages/SystemUI/res/anim/recents_from_app_exit.xml
new file mode 100644
index 0000000..1447a5a
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_app_exit.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="normal">
+
+    <!-- Animate the view out only after recents is visible -->
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+           android:fillEnabled="true"
+           android:fillBefore="true" android:fillAfter="true"
+           android:interpolator="@android:interpolator/fast_out_slow_in"
+           android:duration="1"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
index 4bd7e82..bac8cb6 100644
--- a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
+++ b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
@@ -20,9 +20,9 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top">
-  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
          android:fillEnabled="true"
          android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/accelerate_cubic"
-         android:duration="250"/>
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="100"/>
 </set>
diff --git a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
index becc9d0..b0f8807 100644
--- a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
+++ b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
@@ -23,6 +23,6 @@
   <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
          android:fillEnabled="true"
          android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/decelerate_cubic"
-         android:duration="250"/>
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="100"/>
 </set>
diff --git a/packages/SystemUI/res/anim/recents_from_unknown_enter.xml b/packages/SystemUI/res/anim/recents_from_unknown_enter.xml
new file mode 100644
index 0000000..f68a143
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_unknown_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="200"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_from_unknown_exit.xml b/packages/SystemUI/res/anim/recents_from_unknown_exit.xml
new file mode 100644
index 0000000..31cf26a
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_unknown_exit.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="normal">
+  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="200"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_to_launcher_enter.xml b/packages/SystemUI/res/anim/recents_to_launcher_enter.xml
new file mode 100644
index 0000000..2857c04
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_to_launcher_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="normal">
+  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="150"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_to_launcher_exit.xml b/packages/SystemUI/res/anim/recents_to_launcher_exit.xml
new file mode 100644
index 0000000..1139e72
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_to_launcher_exit.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="150"/>
+</set>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1ef5bcd..b39fa84 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -113,12 +113,16 @@
     <!-- The min animation duration for animating views that are newly visible. -->
     <integer name="recents_filter_animate_new_views_min_duration">125</integer>
     <!-- The min animation duration for animating the task bar in. -->
-    <integer name="recents_animate_task_bar_enter_duration">250</integer>
+    <integer name="recents_animate_task_bar_enter_duration">275</integer>
     <!-- The animation delay for animating the first task in. This should roughly be the animation
      duration of the transition in to recents. -->
     <integer name="recents_animate_task_bar_enter_delay">225</integer>
     <!-- The min animation duration for animating the task bar out. -->
     <integer name="recents_animate_task_bar_exit_duration">125</integer>
+    <!-- The min animation duration for animating the task in when transitioning from home. -->
+    <integer name="recents_animate_task_enter_from_home_duration">325</integer>
+    <!-- The animation stagger to apply to each task animation when transitioning from home. -->
+    <integer name="recents_animate_task_enter_from_home_delay">16</integer>
     <!-- The min animation duration for animating the nav bar scrim in. -->
     <integer name="recents_nav_bar_scrim_enter_duration">400</integer>
     <!-- The animation duration for animating the removal of a task view. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 35bc7e3..fdf7f8d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -235,9 +235,6 @@
     <!-- The amount of highlight to make on each task view. -->
     <dimen name="recents_task_view_highlight">1dp</dimen>
 
-    <!-- The amount of space a user has to scroll to dismiss any info panes. -->
-    <dimen name="recents_task_stack_scroll_dismiss_info_pane_distance">50dp</dimen>
-
     <!-- The height of the search bar space. -->
     <dimen name="recents_search_bar_space_height">64dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 2f6d58f..6d44f06 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -27,7 +27,6 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -35,16 +34,12 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.Surface;
-import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
 import com.android.systemui.R;
 
+import java.lang.ref.WeakReference;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -129,8 +124,10 @@
     final public static int MSG_TOGGLE_RECENTS = 6;
     final public static int MSG_START_ENTER_ANIMATION = 7;
 
-    final public static String EXTRA_ANIMATING_WITH_THUMBNAIL = "recents.animatingWithThumbnail";
-    final public static String EXTRA_FROM_ALT_TAB = "recents.triggeredFromAltTab";
+    final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome";
+    final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail";
+    final public static String EXTRA_FROM_APP_FULL_SCREENSHOT = "recents.thumbnail";
+    final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
     final public static String KEY_CONFIGURATION_DATA = "recents.data.updateForConfiguration";
     final public static String KEY_WINDOW_RECT = "recents.windowRect";
     final public static String KEY_SYSTEM_INSETS = "recents.systemInsets";
@@ -138,7 +135,6 @@
     final public static String KEY_TWO_TASK_STACK_RECT = "recents.twoCountTaskRect";
     final public static String KEY_MULTIPLE_TASK_STACK_RECT = "recents.multipleCountTaskRect";
 
-
     final static int sMinToggleDelay = 425;
 
     final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
@@ -146,6 +142,8 @@
     final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
     final static String sRecentsService = "com.android.systemui.recents.RecentsService";
 
+    static Bitmap sLastScreenshot;
+
     Context mContext;
     SystemServicesProxy mSystemServicesProxy;
 
@@ -213,15 +211,19 @@
         if (Console.Enabled) {
             Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]");
         }
+
         if (mServiceIsBound && mBootCompleted) {
-            // Notify recents to close it
-            try {
-                Bundle data = new Bundle();
-                Message msg = Message.obtain(null, MSG_HIDE_RECENTS, triggeredFromAltTab ? 1 : 0, 0);
-                msg.setData(data);
-                mService.send(msg);
-            } catch (RemoteException re) {
-                re.printStackTrace();
+            if (isRecentsTopMost(null)) {
+                // Notify recents to close it
+                try {
+                    Bundle data = new Bundle();
+                    Message msg = Message.obtain(null, MSG_HIDE_RECENTS,
+                            triggeredFromAltTab ? 1 : 0, 0);
+                    msg.setData(data);
+                    mService.send(msg);
+                } catch (RemoteException re) {
+                    re.printStackTrace();
+                }
             }
         }
     }
@@ -343,80 +345,6 @@
         }
     }
 
-    /** Converts from the device rotation to the degree */
-    float getDegreesForRotation(int value) {
-        switch (value) {
-            case Surface.ROTATION_90:
-                return 360f - 90f;
-            case Surface.ROTATION_180:
-                return 360f - 180f;
-            case Surface.ROTATION_270:
-                return 360f - 270f;
-        }
-        return 0f;
-    }
-
-    /** Takes a screenshot of the surface */
-    Bitmap takeScreenshot(Display display) {
-        DisplayMetrics dm = new DisplayMetrics();
-        display.getRealMetrics(dm);
-        float[] dims = {dm.widthPixels, dm.heightPixels};
-        float degrees = getDegreesForRotation(display.getRotation());
-        boolean requiresRotation = (degrees > 0);
-        if (requiresRotation) {
-            // Get the dimensions of the device in its native orientation
-            Matrix m = new Matrix();
-            m.preRotate(-degrees);
-            m.mapPoints(dims);
-            dims[0] = Math.abs(dims[0]);
-            dims[1] = Math.abs(dims[1]);
-        }
-        return SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
-    }
-
-    /** Creates the activity options for a thumbnail transition. */
-    ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) {
-        // Loading from thumbnail
-        Bitmap thumbnail;
-        Bitmap firstThumbnail = loadFirstTaskThumbnail();
-        if (firstThumbnail != null) {
-            // Create the thumbnail
-            thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
-                    Bitmap.Config.ARGB_8888);
-            int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight());
-            Canvas c = new Canvas(thumbnail);
-            c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size),
-                    new Rect(0, 0, taskRect.width(), taskRect.height()), null);
-            c.setBitmap(null);
-            // Recycle the old thumbnail
-            firstThumbnail.recycle();
-        } else {
-            // Load the thumbnail from the screenshot if can't get one from the system
-            WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-            Display display = wm.getDefaultDisplay();
-            Bitmap screenshot = takeScreenshot(display);
-            if (screenshot != null) {
-                Resources res = mContext.getResources();
-                int size = Math.min(screenshot.getWidth(), screenshot.getHeight());
-                int statusBarHeight = res.getDimensionPixelSize(
-                        com.android.internal.R.dimen.status_bar_height);
-                thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
-                        Bitmap.Config.ARGB_8888);
-                Canvas c = new Canvas(thumbnail);
-                c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight +
-                        size), new Rect(0, 0, taskRect.width(), taskRect.height()), null);
-                c.setBitmap(null);
-                // Recycle the temporary screenshot
-                screenshot.recycle();
-            } else {
-                return null;
-            }
-        }
-
-        return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, thumbnail,
-                taskRect.left, taskRect.top, this);
-    }
-
     /** Returns whether the recents is currently running */
     boolean isRecentsTopMost(AtomicBoolean isHomeTopMost) {
         SystemServicesProxy ssp = mSystemServicesProxy;
@@ -462,10 +390,12 @@
                 mService.send(msg);
 
                 // Time this path
-                Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                        Constants.Log.App.TimeRecentsStartupKey, "sendToggleRecents");
-                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                        Constants.Log.App.TimeRecentsLaunchKey, "sendToggleRecents");
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                            Constants.Log.App.TimeRecentsStartupKey, "sendToggleRecents");
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                            Constants.Log.App.TimeRecentsLaunchKey, "sendToggleRecents");
+                }
             } catch (RemoteException re) {
                 re.printStackTrace();
             }
@@ -486,6 +416,68 @@
         }
     }
 
+    /**
+     * Creates the activity options for a unknown state->recents transition.
+     */
+    ActivityOptions getUnknownTransitionActivityOptions() {
+        // Reset the last screenshot
+        consumeLastScreenshot();
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_unknown_enter,
+                R.anim.recents_from_unknown_exit, mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for a home->recents transition.
+     */
+    ActivityOptions getHomeTransitionActivityOptions() {
+        // Reset the last screenshot
+        consumeLastScreenshot();
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_launcher_enter,
+                R.anim.recents_from_launcher_exit, mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for an app->recents transition.  If this method sets the static
+     * screenshot, then we will use that for the transition.
+     */
+    ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) {
+        // Recycle the last screenshot
+        consumeLastScreenshot();
+
+        // Take the full screenshot
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            sLastScreenshot = mSystemServicesProxy.takeScreenshot();
+            if (sLastScreenshot != null) {
+                return ActivityOptions.makeCustomAnimation(mContext,
+                        R.anim.recents_from_app_enter,
+                        R.anim.recents_from_app_exit, mHandler, this);
+            }
+        }
+
+        // If the screenshot fails, then load the first task thumbnail and use that
+        Bitmap firstThumbnail = loadFirstTaskThumbnail();
+        if (firstThumbnail != null) {
+            // Create the new thumbnail for the animation down
+            // XXX: We should find a way to optimize this so we don't need to create a new bitmap
+            Bitmap thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
+                    Bitmap.Config.ARGB_8888);
+            int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight());
+            Canvas c = new Canvas(thumbnail);
+            c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size),
+                    new Rect(0, 0, taskRect.width(), taskRect.height()), null);
+            c.setBitmap(null);
+            // Recycle the old thumbnail
+            firstThumbnail.recycle();
+            return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
+                    thumbnail, taskRect.left, taskRect.top, this);
+        }
+
+        // If both the screenshot and thumbnail fails, then just fall back to the default transition
+        return getUnknownTransitionActivityOptions();
+    }
+
     /** Starts the recents activity */
     void startRecentsActivity(boolean isTopTaskHome) {
         // If Recents is not the front-most activity and we should animate into it.  If
@@ -503,7 +495,11 @@
             // Try starting with a thumbnail transition
             ActivityOptions opts = getThumbnailTransitionActivityOptions(taskRect);
             if (opts != null) {
-                startAlternateRecentsActivity(opts, true);
+                if (sLastScreenshot != null) {
+                    startAlternateRecentsActivity(opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
+                } else {
+                    startAlternateRecentsActivity(opts, EXTRA_FROM_APP_THUMBNAIL);
+                }
             } else {
                 // Fall through below to the non-thumbnail transition
                 useThumbnailTransition = false;
@@ -512,25 +508,33 @@
 
         // If there is no thumbnail transition, then just use a generic transition
         if (!useThumbnailTransition) {
-            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
-                    R.anim.recents_from_launcher_enter,
-                    R.anim.recents_from_launcher_exit, mHandler, this);
-            startAlternateRecentsActivity(opts, false);
+            if (Constants.DebugFlags.App.EnableHomeTransition) {
+                ActivityOptions opts = getHomeTransitionActivityOptions();
+                startAlternateRecentsActivity(opts, EXTRA_FROM_HOME);
+            } else {
+                ActivityOptions opts = getUnknownTransitionActivityOptions();
+                startAlternateRecentsActivity(opts, null);
+            }
         }
 
-        Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity");
+        if (Console.Enabled) {
+            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                    Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity");
+        }
         mLastToggleTime = System.currentTimeMillis();
     }
 
     /** Starts the recents activity */
-    void startAlternateRecentsActivity(ActivityOptions opts, boolean animatingWithThumbnail) {
+    void startAlternateRecentsActivity(ActivityOptions opts, String extraFlag) {
         Intent intent = new Intent(sToggleRecentsAction);
         intent.setClassName(sRecentsPackage, sRecentsActivity);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        intent.putExtra(EXTRA_ANIMATING_WITH_THUMBNAIL, animatingWithThumbnail);
-        intent.putExtra(EXTRA_FROM_ALT_TAB, mTriggeredFromAltTab);
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+        if (extraFlag != null) {
+            intent.putExtra(extraFlag, true);
+        }
+        intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
         if (opts != null) {
             mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
                     UserHandle.USER_CURRENT));
@@ -539,6 +543,18 @@
         }
     }
 
+    /** Returns the last screenshot taken, this will be called by the RecentsActivity. */
+    public static Bitmap getLastScreenshot() {
+        return sLastScreenshot;
+    }
+
+    /** Recycles the last screenshot taken, this will be called by the RecentsActivity. */
+    public static void consumeLastScreenshot() {
+        if (sLastScreenshot != null) {
+            sLastScreenshot.recycle();
+            sLastScreenshot = null;
+        }
+    }
 
     /**** OnAnimationStartedListener Implementation ****/
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 147ff62..cd4d206 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -25,6 +25,10 @@
         public static final boolean Verbose = false;
 
         public static class App {
+            // Enables the home->Recents transition
+            public static final boolean EnableHomeTransition = false;
+            // Enables the screenshot app->Recents transition
+            public static final boolean EnableScreenshotAppTransition = false;
             // Enables the filtering of tasks according to their grouping
             public static final boolean EnableTaskFiltering = false;
             // Enables clipping of tasks against each other
@@ -52,8 +56,11 @@
         public static class App {
             public static final String TimeRecentsStartupKey = "startup";
             public static final String TimeRecentsLaunchKey = "launchTask";
-            public static final boolean TimeRecentsStartup = false;
-            public static final boolean TimeRecentsLaunchTask = false;
+            public static final String TimeRecentsScreenshotTransitionKey = "screenshot";
+            public static final boolean TimeRecentsStartup = true;
+            public static final boolean TimeRecentsLaunchTask = true;
+            public static final boolean TimeRecentsScreenshotTransition = true;
+
 
             public static final boolean RecentsComponent = false;
             public static final boolean TaskDataLoader = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 96344d5..9800271 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -22,11 +22,9 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.Pair;
 import android.view.Gravity;
@@ -34,17 +32,17 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
 import com.android.systemui.recents.model.SpaceNode;
 import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.FullScreenTransitionView;
 import com.android.systemui.recents.views.RecentsView;
+import com.android.systemui.recents.views.ViewAnimation;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Set;
 
 /** Our special app widget host */
 class RecentsAppWidgetHost extends AppWidgetHost {
@@ -68,11 +66,16 @@
 
 /* Activity */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
-        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
+        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
+        FullScreenTransitionView.FullScreenTransitionViewCallbacks {
+
     FrameLayout mContainerView;
     RecentsView mRecentsView;
     View mEmptyView;
     View mNavBarScrimView;
+    FullScreenTransitionView mFullScreenshotView;
+
+    RecentsConfiguration mConfig;
 
     AppWidgetHost mAppWidgetHost;
     AppWidgetProviderInfo mSearchAppWidgetInfo;
@@ -108,8 +111,15 @@
                     // Dismiss recents, launching the focused task
                     dismissRecentsIfVisible();
                 } else {
-                    // Otherwise, just finish the activity without launching any other activities
-                    finish();
+                    // If we are mid-animation into Recents, then reverse it and finish
+                    if (mFullScreenshotView == null ||
+                            !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+                        // Otherwise, just finish the activity without launching any other activities
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
+                                null, mFinishRunnable, null);
+                        mRecentsView.startOnExitAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    }
                 }
             } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
                 // Try and unfilter and filtered stacks
@@ -119,7 +129,9 @@
                 }
             } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
                 // Try and start the enter animation (or restart it on configuration changed)
-                mRecentsView.startOnEnterAnimation();
+                mRecentsView.startOnEnterAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView));
+                // Call our callback
+                onEnterAnimationTriggered();
             }
         }
     };
@@ -132,14 +144,27 @@
         }
     };
 
+    // A runnable to finish the Recents activity
+    Runnable mFinishRunnable = new Runnable() {
+        @Override
+        public void run() {
+            finish();
+            overridePendingTransition(R.anim.recents_to_launcher_enter,
+                    R.anim.recents_to_launcher_exit);
+        }
+    };
+
     /** Updates the set of recent tasks */
     void updateRecentsTasks(Intent launchIntent) {
         // Update the configuration based on the launch intent
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        config.launchedWithThumbnailAnimation = launchIntent.getBooleanExtra(
-                AlternateRecentsComponent.EXTRA_ANIMATING_WITH_THUMBNAIL, false);
-        config.launchedFromAltTab = launchIntent.getBooleanExtra(
-                AlternateRecentsComponent.EXTRA_FROM_ALT_TAB, false);
+        mConfig.launchedFromHome = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_FROM_HOME, false);
+        mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false);
+        mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false);
+        mConfig.launchedWithAltTab = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
 
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
@@ -165,7 +190,6 @@
     /** Attempts to allocate and bind the search bar app widget */
     void bindSearchBarAppWidget() {
         if (Constants.DebugFlags.App.EnableSearchLayout) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
             SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
 
             // Reset the host view and widget info
@@ -173,7 +197,7 @@
             mSearchAppWidgetInfo = null;
 
             // Try and load the app widget id from the settings
-            int appWidgetId = config.searchBarAppWidgetId;
+            int appWidgetId = mConfig.searchBarAppWidgetId;
             if (appWidgetId >= 0) {
                 mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId);
                 if (mSearchAppWidgetInfo == null) {
@@ -203,7 +227,7 @@
                     }
 
                     // Save the app widget id into the settings
-                    config.updateSearchBarAppWidgetId(this, widgetInfo.first);
+                    mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
                     mSearchAppWidgetInfo = widgetInfo.second;
                 }
             }
@@ -213,8 +237,7 @@
     /** Creates the search bar app widget view */
     void addSearchBarAppWidgetView() {
         if (Constants.DebugFlags.App.EnableSearchLayout) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
-            int appWidgetId = config.searchBarAppWidgetId;
+            int appWidgetId = mConfig.searchBarAppWidgetId;
             if (appWidgetId >= 0) {
                 if (Console.Enabled) {
                     Console.log(Constants.Log.App.SystemUIHandshake,
@@ -240,9 +263,19 @@
     /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
     boolean dismissRecentsIfVisible() {
         if (mVisible) {
-            if (!mRecentsView.launchFocusedTask()) {
-                if (!mRecentsView.launchFirstTask()) {
-                    finish();
+            // If we are mid-animation into Recents, then reverse it and finish
+            if (mFullScreenshotView == null ||
+                    !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+                // If we have a focused task, then launch that task
+                if (!mRecentsView.launchFocusedTask()) {
+                    // If there are any tasks, then launch the first task
+                    if (!mRecentsView.launchFirstTask()) {
+                        // We really shouldn't hit this, but if we do, just animate out (aka. finish)
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                                null, mFinishRunnable, null);
+                        mRecentsView.startOnExitAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    }
                 }
             }
             return true;
@@ -264,7 +297,7 @@
 
         // Initialize the loader and the configuration
         RecentsTaskLoader.initialize(this);
-        RecentsConfiguration.reinitialize(this);
+        mConfig = RecentsConfiguration.reinitialize(this);
 
         // Initialize the widget host (the host id is static and does not change)
         mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId, this);
@@ -286,16 +319,29 @@
         mNavBarScrimView.setLayoutParams(new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM));
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenshotView = new FullScreenTransitionView(this, this);
+            mFullScreenshotView.setLayoutParams(new FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        }
 
         mContainerView = new FrameLayout(this);
         mContainerView.addView(mRecentsView);
         mContainerView.addView(mEmptyView);
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mContainerView.addView(mFullScreenshotView);
+        }
         mContainerView.addView(mNavBarScrimView);
         setContentView(mContainerView);
 
         // Update the recent tasks
         updateRecentsTasks(getIntent());
 
+        // Prepare the screenshot transition if necessary
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+        }
+
         // Bind the search app widget when we first start up
         bindSearchBarAppWidget();
         // Add the search bar layout
@@ -319,7 +365,9 @@
 
     void onConfigurationChange() {
         // Try and start the enter animation (or restart it on configuration changed)
-        mRecentsView.startOnEnterAnimation();
+        mRecentsView.startOnEnterAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView));
+        // Call our callback
+        onEnterAnimationTriggered();
     }
 
     @Override
@@ -338,11 +386,16 @@
 
         // Initialize the loader and the configuration
         RecentsTaskLoader.initialize(this);
-        RecentsConfiguration.reinitialize(this);
+        mConfig = RecentsConfiguration.reinitialize(this);
 
         // Update the recent tasks
         updateRecentsTasks(intent);
 
+        // Prepare the screenshot transition if necessary
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+        }
+
         // Don't attempt to rebind the search bar widget, but just add the search bar layout
         addSearchBarAppWidgetView();
     }
@@ -356,8 +409,7 @@
         super.onStart();
 
         // Start listening for widget package changes if there is one bound
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.searchBarAppWidgetId >= 0) {
+        if (mConfig.searchBarAppWidgetId >= 0) {
             mAppWidgetHost.startListening();
         }
 
@@ -431,8 +483,7 @@
         super.onStop();
 
         // Stop listening for widget package changes if there was one bound
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.searchBarAppWidgetId >= 0) {
+        if (mConfig.searchBarAppWidgetId >= 0) {
             mAppWidgetHost.stopListening();
         }
 
@@ -471,41 +522,70 @@
 
     @Override
     public void onBackPressed() {
-        // Unfilter any stacks
-        if (!mRecentsView.unfilterFilteredStacks()) {
-            if (!mRecentsView.launchFirstTask()) {
-                super.onBackPressed();
+        // If we are mid-animation into Recents, then reverse it and finish
+        if (mFullScreenshotView == null ||
+                !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+            // If we are currently filtering in any stacks, unfilter them first
+            if (!mRecentsView.unfilterFilteredStacks()) {
+                if (mConfig.launchedFromHome) {
+                    // Just start the animation out of recents
+                    ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                            null, mFinishRunnable, null);
+                    mRecentsView.startOnExitAnimation(
+                            new ViewAnimation.TaskViewExitContext(exitTrigger));
+                } else {
+                    // Otherwise, try and launch the first task
+                    if (!mRecentsView.launchFirstTask()) {
+                        // If there are no tasks, then just finish recents
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                                null, mFinishRunnable, null);
+                        mRecentsView.startOnExitAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    }
+                }
             }
         }
     }
 
-    @Override
     public void onEnterAnimationTriggered() {
         // Fade in the scrim
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.hasNavBarScrim()) {
+        if (mConfig.hasNavBarScrim()) {
             mNavBarScrimView.setVisibility(View.VISIBLE);
             mNavBarScrimView.setAlpha(0f);
             mNavBarScrimView.animate().alpha(1f)
-                    .setStartDelay(config.taskBarEnterAnimDelay)
-                    .setDuration(config.navBarScrimEnterDuration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
+                    .setStartDelay(mConfig.taskBarEnterAnimDelay)
+                    .setDuration(mConfig.navBarScrimEnterDuration)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
                     .withLayer()
                     .start();
         }
     }
 
     @Override
+    public void onEnterAnimationComplete(boolean canceled) {
+        if (!canceled) {
+            // Reset the full screenshot transition view
+            if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+                mFullScreenshotView.reset();
+            }
+
+            // XXX: We should clean up the screenshot in this case as well, but it needs to happen
+            //      after to animate up
+        }
+        // Recycle the full screen screenshot
+        AlternateRecentsComponent.consumeLastScreenshot();
+    }
+
+    @Override
     public void onTaskLaunching(boolean isTaskInStackBounds) {
         mTaskLaunched = true;
 
         // Fade out the scrim
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (!isTaskInStackBounds && config.hasNavBarScrim()) {
+        if (!isTaskInStackBounds && mConfig.hasNavBarScrim()) {
             mNavBarScrimView.animate().alpha(0f)
                     .setStartDelay(0)
-                    .setDuration(config.taskBarExitAnimDuration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
+                    .setDuration(mConfig.taskBarExitAnimDuration)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
                     .withLayer()
                     .start();
         }
@@ -513,12 +593,11 @@
 
     @Override
     public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
-        if (appWidgetId > -1 && appWidgetId == config.searchBarAppWidgetId) {
+        if (appWidgetId > -1 && appWidgetId == mConfig.searchBarAppWidgetId) {
             // The search provider may have changed, so just delete the old widget and bind it again
             ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
-            config.updateSearchBarAppWidgetId(this, -1);
+            mConfig.updateSearchBarAppWidgetId(this, -1);
             // Load the widget again
             bindSearchBarAppWidget();
             addSearchBarAppWidgetView();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 0cf6ee6..a0c5253 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -37,27 +37,39 @@
 
     DisplayMetrics mDisplayMetrics;
 
-    public Rect systemInsets = new Rect();
-    public Rect displayRect = new Rect();
-
-    boolean isLandscape;
-    boolean transposeRecentsLayoutWithOrientation;
-    int searchBarAppWidgetId = -1;
-
+    /** Animations */
     public float animationPxMovementPerSecond;
 
+    /** Interpolators */
     public Interpolator fastOutSlowInInterpolator;
     public Interpolator fastOutLinearInInterpolator;
     public Interpolator linearOutSlowInInterpolator;
+    public Interpolator quintOutInterpolator;
 
+    /** Filtering */
     public int filteringCurrentViewsMinAnimDuration;
     public int filteringNewViewsMinAnimDuration;
 
-    public int taskStackScrollDismissInfoPaneDistance;
-    public int taskStackMaxDim;
-    public float taskStackWidthPaddingPct;
-    public int taskStackTopPaddingPx;
+    /** Insets */
+    public Rect systemInsets = new Rect();
+    public Rect displayRect = new Rect();
 
+    /** Layout */
+    boolean isLandscape;
+    boolean transposeRecentsLayoutWithOrientation;
+
+    /** Search bar */
+    int searchBarAppWidgetId = -1;
+    public int searchBarSpaceHeightPx;
+
+    /** Task stack */
+    public int taskStackMaxDim;
+    public int taskStackTopPaddingPx;
+    public float taskStackWidthPaddingPct;
+
+    /** Task view animation and styles */
+    public int taskViewEnterFromHomeDuration;
+    public int taskViewEnterFromHomeDelay;
     public int taskViewRemoveAnimDuration;
     public int taskViewRemoveAnimTranslationXPx;
     public int taskViewTranslationZMinPx;
@@ -66,23 +78,28 @@
     public int taskViewRoundedCornerRadiusPx;
     public int taskViewHighlightPx;
 
-    public int searchBarSpaceHeightPx;
-
+    /** Task bar colors */
     public int taskBarViewDefaultBackgroundColor;
     public int taskBarViewDefaultTextColor;
     public int taskBarViewLightTextColor;
     public int taskBarViewDarkTextColor;
     public int taskBarViewHighlightColor;
 
+    /** Task bar animations */
     public int taskBarEnterAnimDuration;
     public int taskBarEnterAnimDelay;
     public int taskBarExitAnimDuration;
 
+    /** Nav bar scrim */
     public int navBarScrimEnterDuration;
 
-    public boolean launchedFromAltTab;
-    public boolean launchedWithThumbnailAnimation;
+    /** Launch states */
+    public boolean launchedWithAltTab;
+    public boolean launchedFromAppWithThumbnail;
+    public boolean launchedFromAppWithScreenshot;
+    public boolean launchedFromHome;
 
+    /** Dev options */
     public boolean developerOptionsEnabled;
 
     /** Private constructor */
@@ -108,33 +125,54 @@
         DisplayMetrics dm = res.getDisplayMetrics();
         mDisplayMetrics = dm;
 
-        isLandscape = res.getConfiguration().orientation ==
-                Configuration.ORIENTATION_LANDSCAPE;
-        transposeRecentsLayoutWithOrientation =
-                res.getBoolean(R.bool.recents_transpose_layout_with_orientation);
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout,
-                    "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
-                    Console.AnsiGreen);
-        }
-
-        displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
+        // Animations
         animationPxMovementPerSecond =
                 res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
+
+        // Interpolators
+        fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_slow_in);
+        fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_linear_in);
+        linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.linear_out_slow_in);
+        quintOutInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.decelerate_quint);
+
+        // Filtering
         filteringCurrentViewsMinAnimDuration =
                 res.getInteger(R.integer.recents_filter_animate_current_views_min_duration);
         filteringNewViewsMinAnimDuration =
                 res.getInteger(R.integer.recents_filter_animate_new_views_min_duration);
 
-        taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize(
-                R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance);
-        taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
+        // Insets
+        displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
 
+        // Layout
+        isLandscape = res.getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE;
+        transposeRecentsLayoutWithOrientation =
+                res.getBoolean(R.bool.recents_transpose_layout_with_orientation);
+
+        // Search bar
+        searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
+
+        // Update the search widget id
+        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
+        searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
+
+        // Task stack
         TypedValue widthPaddingPctValue = new TypedValue();
         res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true);
         taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();
+        taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
         taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding);
 
+        // Task view animation and styles
+        taskViewEnterFromHomeDuration =
+                res.getInteger(R.integer.recents_animate_task_enter_from_home_duration);
+        taskViewEnterFromHomeDelay =
+                res.getInteger(R.integer.recents_animate_task_enter_from_home_delay);
         taskViewRemoveAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_view_remove_duration);
         taskViewRemoveAnimTranslationXPx =
@@ -148,8 +186,7 @@
         taskViewShadowOutlineBottomInsetPx =
                 res.getDimensionPixelSize(R.dimen.recents_task_view_shadow_outline_bottom_inset);
 
-        searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
-
+        // Task bar colors
         taskBarViewDefaultBackgroundColor =
                 res.getColor(R.color.recents_task_bar_default_background_color);
         taskBarViewDefaultTextColor =
@@ -161,6 +198,7 @@
         taskBarViewHighlightColor =
                 res.getColor(R.color.recents_task_bar_highlight_color);
 
+        // Task bar animations
         taskBarEnterAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_bar_enter_duration);
         taskBarEnterAnimDelay =
@@ -168,24 +206,20 @@
         taskBarExitAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_bar_exit_duration);
 
+        // Nav bar scrim
         navBarScrimEnterDuration =
                 res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
 
-        fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                        com.android.internal.R.interpolator.fast_out_slow_in);
-        fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.fast_out_linear_in);
-        linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.linear_out_slow_in);
-
         // Check if the developer options are enabled
         ContentResolver cr = context.getContentResolver();
         developerOptionsEnabled = Settings.Global.getInt(cr,
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
 
-        // Update the search widget id
-        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
-        searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.MeasureAndLayout,
+                    "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
+                    Console.AnsiGreen);
+        }
     }
 
     /** Updates the system insets */
@@ -204,8 +238,10 @@
     /** Called when the configuration has changed, and we want to reset any configuration specific
      * members. */
     public void updateOnConfigurationChange() {
-        launchedFromAltTab = false;
-        launchedWithThumbnailAnimation = false;
+        launchedWithAltTab = false;
+        launchedFromAppWithThumbnail = false;
+        launchedFromAppWithScreenshot = false;
+        launchedFromHome = false;
     }
 
     /** Returns whether the search bar app widget exists. */
@@ -257,15 +293,4 @@
             searchBarSpaceBounds.set(0, 0, width, searchBarSpaceHeightPx);
         }
     }
-
-    /** Converts from DPs to PXs */
-    public int pxFromDp(float size) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                size, mDisplayMetrics));
-    }
-    /** Converts from SPs to PXs */
-    public int pxFromSp(float size) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
-                size, mDisplayMetrics));
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index 0c2c11d..1cc57ef 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -132,10 +132,12 @@
             context.sendBroadcast(intent);
 
             // Time this path
-            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
-            Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                    Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
+            if (Console.Enabled) {
+                Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                        Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
+                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                        Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
+            }
         } else if (msg.what == AlternateRecentsComponent.MSG_START_ENTER_ANIMATION) {
             // Send a broadcast to start the enter animation
             Intent intent = new Intent(RecentsService.ACTION_START_ENTER_ANIMATION);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index 4685186..dbcdb94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -478,7 +478,7 @@
                 // Load the thumbnail (if possible and not the foremost task, from the cache)
                 if (!isForemostTask) {
                     task.thumbnail = mThumbnailCache.get(task.key);
-                    if (task.thumbnail != null) {
+                    if (task.thumbnail != null && !tasksToForceLoad.contains(task)) {
                         // Even though we get things from the cache, we should update them if
                         // they've changed in the bg
                         tasksToForceLoad.add(task);
@@ -489,6 +489,7 @@
                         Console.log(Constants.Log.App.TaskDataLoader,
                                 "[RecentsTaskLoader|loadingTaskThumbnail]");
                     }
+
                     task.thumbnail = ssp.getTaskThumbnail(task.key.id);
                     if (task.thumbnail != null) {
                         task.thumbnail.setHasAlpha(false);
@@ -512,20 +513,6 @@
                     "" + (System.currentTimeMillis() - t1) + "ms");
         }
 
-        /*
-        // Get all the stacks
-        t1 = System.currentTimeMillis();
-        List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos();
-        Console.log(Constants.Log.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms");
-        Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stacks]", "" + tasks.size());
-        for (ActivityManager.StackInfo s : stackInfos) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [RecentsTaskLoader|stack]", s.toString());
-            if (stacks.containsKey(s.stackId)) {
-                stacks.get(s.stackId).setRect(s.bounds);
-            }
-        }
-        */
-
         // Start the task loader
         mLoader.start(context);
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java
new file mode 100644
index 0000000..2f89e6d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.content.Context;
+
+/**
+ * A ref counted trigger that does some logic when the count is first incremented, or last
+ * decremented.  Not thread safe as it's not currently needed.
+ */
+public class ReferenceCountedTrigger {
+
+    Context mContext;
+    int mCount;
+    Runnable mFirstIncRunnable;
+    Runnable mLastDecRunnable;
+    Runnable mErrorRunnable;
+
+    // Convenience runnables
+    Runnable mIncrementRunnable = new Runnable() {
+        @Override
+        public void run() {
+            increment();
+        }
+    };
+    Runnable mDecrementRunnable = new Runnable() {
+        @Override
+        public void run() {
+            decrement();
+        }
+    };
+
+    public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable,
+                                   Runnable lastDecRunnable, Runnable errorRunanable) {
+        mContext = context;
+        mFirstIncRunnable = firstIncRunnable;
+        mLastDecRunnable = lastDecRunnable;
+        mErrorRunnable = errorRunanable;
+    }
+
+    /** Increments the ref count */
+    public void increment() {
+        if (mCount == 0 && mFirstIncRunnable != null) {
+            mFirstIncRunnable.run();
+        }
+        mCount++;
+    }
+
+    /** Convenience method to increment this trigger as a runnable */
+    public Runnable incrementAsRunnable() {
+        return mIncrementRunnable;
+    }
+
+    /** Decrements the ref count */
+    public void decrement() {
+        mCount--;
+        if (mCount == 0 && mLastDecRunnable != null) {
+            mLastDecRunnable.run();
+        } else if (mCount < 0) {
+            if (mErrorRunnable != null) {
+                mErrorRunnable.run();
+            } else {
+                new Throwable("Invalid ref count").printStackTrace();
+                Console.logError(mContext, "Invalid ref count");
+            }
+        }
+    }
+
+    /** Convenience method to decrement this trigger as a runnable */
+    public Runnable decrementAsRunnable() {
+        return mDecrementRunnable;
+    }
+
+    /** Returns the current ref count */
+    public int getCount() {
+        return mCount;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
index 7a3ffb8..f532aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
@@ -30,13 +30,20 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.graphics.Matrix;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.DisplayMetrics;
 import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -54,6 +61,8 @@
     IPackageManager mIpm;
     UserManager mUm;
     SearchManager mSm;
+    WindowManager mWm;
+    Display mDisplay;
     String mRecentsPackage;
     ComponentName mAssistComponent;
 
@@ -67,6 +76,8 @@
         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mIpm = AppGlobals.getPackageManager();
         mSm = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
+        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
 
         // Resolve the assist intent
@@ -325,4 +336,13 @@
         // Delete the app widget
         host.deleteAppWidgetId(appWidgetId);
     }
+
+    /**
+     * Takes a screenshot of the current surface.
+     */
+    public Bitmap takeScreenshot() {
+        DisplayInfo di = new DisplayInfo();
+        mDisplay.getDisplayInfo(di);
+        return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java
new file mode 100644
index 0000000..ad2fa8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java
@@ -0,0 +1,233 @@
+/*
+ * 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.Canvas;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+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 FullScreenTransitionView extends FrameLayout {
+
+    /** The FullScreenTransitionView callbacks */
+    public interface FullScreenTransitionViewCallbacks {
+        void onEnterAnimationComplete(boolean canceled);
+    }
+
+    RecentsConfiguration mConfig;
+
+    FullScreenTransitionViewCallbacks mCb;
+
+    ImageView mScreenshotView;
+
+    Rect mClipRect = new Rect();
+
+    boolean mIsAnimating;
+    AnimatorSet mEnterAnimation;
+
+    public FullScreenTransitionView(Context context, FullScreenTransitionViewCallbacks cb) {
+        super(context);
+        mConfig = RecentsConfiguration.getInstance();
+        mCb = cb;
+        mScreenshotView = new ImageView(context);
+        mScreenshotView.setScaleType(ImageView.ScaleType.FIT_XY);
+        mScreenshotView.setLayoutParams(new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        addView(mScreenshotView);
+        setClipTop(getClipTop());
+        setClipBottom(getClipBottom());
+        setWillNotDraw(false);
+    }
+
+    /** Sets the top clip */
+    public void setClipTop(int clip) {
+        mClipRect.top = clip;
+        postInvalidateOnAnimation();
+    }
+
+    /** Gets the top clip */
+    public int getClipTop() {
+        return mClipRect.top;
+    }
+
+    /** Sets the bottom clip */
+    public void setClipBottom(int clip) {
+        mClipRect.bottom = clip;
+        postInvalidateOnAnimation();
+    }
+
+    /** Gets the top clip */
+    public int getClipBottom() {
+        return mClipRect.bottom;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+        canvas.clipRect(mClipRect);
+        super.draw(canvas);
+        canvas.restoreToCount(restoreCount);
+    }
+
+    /** Prepares the screenshot view for the transition into Recents */
+    public void prepareAnimateOnEnterRecents(Bitmap screenshot) {
+        if (!mConfig.launchedFromAppWithScreenshot) return;
+
+        if (Console.Enabled) {
+            Console.logStartTracingTime(Constants.Log.App.TimeRecentsScreenshotTransition,
+                    Constants.Log.App.TimeRecentsScreenshotTransitionKey);
+        }
+
+        setClipTop(0);
+        setClipBottom(getMeasuredHeight());
+        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.INVISIBLE);
+        mScreenshotView.setImageDrawable(null);
+    }
+
+    /** Animates this view as it enters recents */
+    public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx,
+                                      final Runnable postAnimRunnable) {
+        if (Console.Enabled) {
+            Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
+                    Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Starting");
+        }
+
+        // Cancel the current animation
+        if (mEnterAnimation != null) {
+            mEnterAnimation.removeAllListeners();
+            mEnterAnimation.cancel();
+        }
+
+        // Calculate the bottom clip
+        float scale = (float) ctx.taskRect.width() / getMeasuredWidth();
+        int translationY = -mConfig.systemInsets.top + ctx.stackRectSansPeek.top +
+                ctx.transform.translationY;
+        int clipBottom = mConfig.systemInsets.top + (int) (ctx.taskRect.height() / scale);
+
+        // Enable the HW Layers on the screenshot view
+        mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+        // Compose the animation
+        mEnterAnimation = new AnimatorSet();
+        mEnterAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Notify any callbacks
+                mCb.onEnterAnimationComplete(false);
+                // Run the given post-anim runnable
+                postAnimRunnable.run();
+                // Mark that we are no longer animating
+                mIsAnimating = false;
+                // Disable the HW Layers on this view
+                setLayerType(View.LAYER_TYPE_NONE, null);
+
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
+                            Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Completed");
+                }
+            }
+        });
+        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.ofFloat(this, "translationY", translationY),
+                ObjectAnimator.ofFloat(this, "scaleX", scale),
+                ObjectAnimator.ofFloat(this, "scaleY", scale)
+        );
+        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();
+            }
+
+            // Compose the animation
+            mEnterAnimation = new AnimatorSet();
+            mEnterAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // Notify any callbacks
+                    mCb.onEnterAnimationComplete(true);
+                    // Run the given post-anim runnable
+                    postAnimRunnable.run();
+                    // Mark that we are no longer animating
+                    mIsAnimating = false;
+                    // Disable the HW Layers on the screenshot view
+                    mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+                }
+            });
+            mEnterAnimation.setDuration(475);
+            mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator);
+            mEnterAnimation.playTogether(
+                    ObjectAnimator.ofInt(this, "clipTop", 0),
+                    ObjectAnimator.ofInt(this, "clipBottom", getMeasuredHeight()),
+                    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 db398b1..a2c250c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -55,9 +55,11 @@
     /** The RecentsView callbacks */
     public interface RecentsViewCallbacks {
         public void onTaskLaunching(boolean isTaskInStackBounds);
-        public void onEnterAnimationTriggered();
     }
 
+    RecentsConfiguration mConfig;
+    LayoutInflater mInflater;
+
     // The space partitioning root of this container
     SpaceNode mBSP;
     // Whether there are any tasks
@@ -67,10 +69,9 @@
     // Recents view callbacks
     RecentsViewCallbacks mCb;
 
-    LayoutInflater mInflater;
-
     public RecentsView(Context context) {
         super(context);
+        mConfig = RecentsConfiguration.getInstance();
         mInflater = LayoutInflater.from(context);
         setWillNotDraw(false);
     }
@@ -160,20 +161,39 @@
     }
 
     /** Requests all task stacks to start their enter-recents animation */
-    public void startOnEnterAnimation() {
-        // Notify callbacks that we are starting the enter animation
-        mCb.onEnterAnimationTriggered();
-
+    public void startOnEnterAnimation(ViewAnimation.TaskViewEnterContext ctx) {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child instanceof TaskStackView) {
                 TaskStackView stackView = (TaskStackView) child;
-                stackView.startOnEnterAnimation();
+                stackView.startOnEnterAnimation(ctx);
             }
         }
     }
 
+    /** Requests all task stacks to start their exit-recents animation */
+    public void startOnExitAnimation(ViewAnimation.TaskViewExitContext ctx) {
+        // Handle the case when there are no views by incrementing and decrementing after all
+        // animations are started.
+        ctx.postAnimationTrigger.increment();
+
+        if (Constants.DebugFlags.App.EnableHomeTransition) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (child instanceof TaskStackView) {
+                    TaskStackView stackView = (TaskStackView) child;
+                    stackView.startOnExitAnimation(ctx);
+                }
+            }
+        }
+
+        // Handle the case when there are no views by incrementing and decrementing after all
+        // animations are started.
+        ctx.postAnimationTrigger.decrement();
+    }
+
     /** Adds the search bar */
     public void setSearchBar(View searchBar) {
         // Create the search bar (and hide it if we have no recent tasks)
@@ -215,10 +235,9 @@
         }
 
         // Get the search bar bounds and measure the search bar layout
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
-            config.getSearchBarBounds(width, height - config.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));
@@ -229,9 +248,9 @@
         // 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();
-        config.getTaskStackBounds(width, height, taskStackBounds);
-        int childWidth = width - config.systemInsets.right;
-        int childHeight = taskStackBounds.height() - config.systemInsets.top;
+        mConfig.getTaskStackBounds(width, height, taskStackBounds);
+        int childWidth = width - mConfig.systemInsets.right;
+        int childHeight = taskStackBounds.height() - mConfig.systemInsets.top;
 
         // Measure each TaskStackView
         int childCount = getChildCount();
@@ -259,23 +278,22 @@
         }
 
         // Get the search bar bounds so that we lay it out
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
-            config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds);
-            mSearchBar.layout(config.systemInsets.left + searchBarSpaceBounds.left,
-                    config.systemInsets.top + searchBarSpaceBounds.top,
-                    config.systemInsets.left + mSearchBar.getMeasuredWidth(),
-                    config.systemInsets.top + mSearchBar.getMeasuredHeight());
+            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());
         }
 
         // 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();
-        config.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
-        left += config.systemInsets.left;
-        top += config.systemInsets.top + taskStackBounds.top;
+        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
@@ -324,8 +342,7 @@
         }
 
         // Update the configuration with the latest system insets and trigger a relayout
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        config.updateSystemInsets(insets.getSystemWindowInsets());
+        mConfig.updateSystemInsets(insets.getSystemWindowInsets());
         requestLayout();
 
         return insets.consumeSystemWindowInsets(false, false, false, true);
@@ -365,6 +382,11 @@
         final Runnable launchRunnable = new Runnable() {
             @Override
             public void run() {
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                            Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity");
+                }
+
                 TaskViewTransform transform;
                 View sourceView = tv;
                 int offsetX = 0;
@@ -374,11 +396,10 @@
                     // If there is no actual task view, then use the stack view as the source view
                     // and then offset to the expected transform rect, but bound this to just
                     // outside the display rect (to ensure we don't animate from too far away)
-                    RecentsConfiguration config = RecentsConfiguration.getInstance();
                     sourceView = stackView;
                     transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
                     offsetX = transform.rect.left;
-                    offsetY = Math.min(transform.rect.top, config.displayRect.height());
+                    offsetY = Math.min(transform.rect.top, mConfig.displayRect.height());
                 } else {
                     transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
                 }
@@ -426,19 +447,23 @@
                     onTaskRemoved(task);
                 }
 
-                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                        Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                            Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
+                }
             }
         };
 
-        Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
+        if (Console.Enabled) {
+            Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                    Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
+        }
 
         // Launch the app right away if there is no task view, otherwise, animate the icon out first
         if (tv == null) {
             post(launchRunnable);
         } else {
-            tv.animateOnLeavingRecents(launchRunnable);
+            tv.animateOnLaunchingTask(launchRunnable);
         }
     }
 
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 c10ddd1..2c637a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -22,8 +22,10 @@
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -36,6 +38,9 @@
 
 /* The task bar view */
 class TaskBarView extends FrameLayout {
+
+    RecentsConfiguration mConfig;
+
     Task mTask;
 
     ImageView mDismissButton;
@@ -61,6 +66,7 @@
 
     public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
 
         // Load the dismiss resources
@@ -70,11 +76,10 @@
 
         // Configure the highlight paint
         if (sHighlightPaint == null) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
             sHighlightPaint = new Paint();
             sHighlightPaint.setStyle(Paint.Style.STROKE);
-            sHighlightPaint.setStrokeWidth(config.taskViewHighlightPx);
-            sHighlightPaint.setColor(config.taskBarViewHighlightColor);
+            sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
+            sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
             sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
             sHighlightPaint.setAntiAlias(true);
         }
@@ -90,11 +95,9 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-
         // Draw the highlight at the top edge (but put the bottom edge just out of view)
-        float offset = config.taskViewHighlightPx / 2f;
-        float radius = config.taskViewRoundedCornerRadiusPx;
+        float offset = mConfig.taskViewHighlightPx / 2f;
+        float radius = mConfig.taskViewRoundedCornerRadiusPx;
         canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
                 getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
     }
@@ -102,7 +105,6 @@
     /** Synchronizes this bar view's properties with the task's transform */
     void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
                                              TaskViewTransform toTransform, int duration) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         if (duration > 0) {
             if (animateFromTransform != null) {
                 mDismissButton.setAlpha(animateFromTransform.dismissAlpha);
@@ -111,18 +113,16 @@
                     .alpha(toTransform.dismissAlpha)
                     .setStartDelay(0)
                     .setDuration(duration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
                     .withLayer()
                     .start();
         } else {
             mDismissButton.setAlpha(toTransform.dismissAlpha);
         }
-        mDismissButton.invalidate();
     }
 
     /** Binds the bar view to the task */
     void rebindToTask(Task t, boolean animate) {
-        RecentsConfiguration configuration = RecentsConfiguration.getInstance();
         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
@@ -137,12 +137,12 @@
         if (Constants.DebugFlags.App.EnableTaskBarThemeColors && tint != 0) {
             setBackgroundColor(tint);
             mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint,
-                    configuration.taskBarViewLightTextColor, configuration.taskBarViewDarkTextColor));
+                    mConfig.taskBarViewLightTextColor, mConfig.taskBarViewDarkTextColor));
             mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint,
                     mLightDismissDrawable, mDarkDismissDrawable));
         } else {
-            setBackgroundColor(configuration.taskBarViewDefaultBackgroundColor);
-            mActivityDescription.setTextColor(configuration.taskBarViewDefaultTextColor);
+            setBackgroundColor(mConfig.taskBarViewDefaultBackgroundColor);
+            mActivityDescription.setTextColor(mConfig.taskBarViewDefaultTextColor);
         }
         if (animate) {
             // XXX: Investigate how expensive it will be to create a second bitmap and crossfade
@@ -155,4 +155,51 @@
         mApplicationIcon.setImageDrawable(null);
         mActivityDescription.setText("");
     }
+
+    /** 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 prepareAnimateEnterRecents() {
+        setVisibility(View.INVISIBLE);
+    }
+
+    /** Animates this task bar as it enters recents */
+    public void animateOnEnterRecents(int delay) {
+        // Animate the task bar of the first task view
+        setVisibility(View.VISIBLE);
+        setTranslationY(-getMeasuredHeight());
+        animate()
+                .translationY(0)
+                .setStartDelay(delay > -1 ? delay : mConfig.taskBarEnterAnimDelay)
+                .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                .setDuration(mConfig.taskBarEnterAnimDuration)
+                .withLayer()
+                .start();
+    }
+
+    /** Animates this task bar as it exits recents */
+    public void animateOnLaunchingTask(final Runnable r) {
+        animate()
+                .translationY(-getMeasuredHeight())
+                .setStartDelay(0)
+                .setInterpolator(mConfig.fastOutLinearInInterpolator)
+                .setDuration(mConfig.taskBarExitAnimDuration)
+                .withLayer()
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        post(r);
+                    }
+                })
+                .start();
+    }
+
+    /** Enable the hw layers on this task view */
+    void enableHwLayers() {
+        mDismissButton.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+    }
+
+    /** Disable the hw layers on this task view */
+    void disableHwLayers() {
+        mDismissButton.setLayerType(View.LAYER_TYPE_NONE, null);
+    }
 }
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 8d9f8be..186565b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -41,6 +41,7 @@
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.RecentsPackageMonitor;
 import com.android.systemui.recents.RecentsTaskLoader;
+import com.android.systemui.recents.ReferenceCountedTrigger;
 import com.android.systemui.recents.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
@@ -62,10 +63,13 @@
         public void onTaskRemoved(Task t);
     }
 
+    RecentsConfiguration mConfig;
+
     TaskStack mStack;
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
     ViewPool<TaskView, Task> mViewPool;
+    ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
 
     // The various rects that define the stack view
     Rect mRect = new Rect();
@@ -83,11 +87,12 @@
     ObjectAnimator mScrollAnimator;
 
     // Optimizations
-    int mHwLayersRefCount;
+    ReferenceCountedTrigger mHwLayersTrigger;
     int mStackViewsAnimationDuration;
     boolean mStackViewsDirty = true;
     boolean mAwaitingFirstLayout = true;
     boolean mStartEnterAnimationRequestedAfterLayout;
+    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
     int[] mTmpVisibleRange = new int[2];
     Rect mTmpRect = new Rect();
     Rect mTmpRect2 = new Rect();
@@ -95,12 +100,40 @@
 
     public TaskStackView(Context context, TaskStack stack) {
         super(context);
+        mConfig = RecentsConfiguration.getInstance();
         mStack = stack;
         mStack.setCallbacks(this);
         mScroller = new OverScroller(context);
         mTouchHandler = new TaskStackViewTouchHandler(context, this);
         mViewPool = new ViewPool<TaskView, Task>(context, this);
         mInflater = LayoutInflater.from(context);
+        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
+            @Override
+            public void run() {
+                // Enable hw layers on each of the children
+                int childCount = getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    TaskView tv = (TaskView) getChildAt(i);
+                    tv.enableHwLayers();
+                }
+            }
+        }, new Runnable() {
+            @Override
+            public void run() {
+                // Disable hw layers on each of the children
+                int childCount = getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    TaskView tv = (TaskView) getChildAt(i);
+                    tv.disableHwLayers();
+                }
+            }
+        }, new Runnable() {
+            @Override
+            public void run() {
+                new Throwable("Invalid hw layers ref count").printStackTrace();
+                Console.logError(getContext(), "Invalid HW layers ref count");
+            }
+        });
     }
 
     /** Sets the callbacks */
@@ -118,7 +151,7 @@
                     "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
         }
         if (!mStackViewsDirty) {
-            invalidate();
+            invalidate(mStackRect);
         }
         if (mAwaitingFirstLayout) {
             // Skip the animation if we are awaiting first layout
@@ -165,7 +198,7 @@
         float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
         transform.scale = scale;
 
-        // Set the translation
+        // Set the y translation
         if (boundedT < 0f) {
             transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
                     numPeekCards) * peekHeight - scaleYOffset);
@@ -174,9 +207,8 @@
         }
 
         // Set the z translation
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        int minZ = config.taskViewTranslationZMinPx;
-        int incZ = config.taskViewTranslationZIncrementPx;
+        int minZ = mConfig.taskViewTranslationZMinPx;
+        int incZ = mConfig.taskViewTranslationZIncrementPx;
         transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
 
         // Set the alphas
@@ -198,16 +230,18 @@
     /**
      * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
      */
-    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
-                                                            int stackScroll,
-                                                            int[] visibleRangeOut,
-                                                            boolean boundTranslationsToRect) {
+    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
+                                       ArrayList<Task> tasks,
+                                       int stackScroll,
+                                       int[] visibleRangeOut,
+                                       boolean boundTranslationsToRect) {
         // XXX: Optimization: Use binary search to find the visible range
 
-        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
         int taskCount = tasks.size();
         int firstVisibleIndex = -1;
         int lastVisibleIndex = -1;
+        taskTransforms.clear();
+        taskTransforms.ensureCapacity(taskCount);
         for (int i = 0; i < taskCount; i++) {
             TaskViewTransform transform = getStackTransform(i, stackScroll);
             taskTransforms.add(transform);
@@ -226,6 +260,19 @@
             visibleRangeOut[0] = firstVisibleIndex;
             visibleRangeOut[1] = lastVisibleIndex;
         }
+    }
+
+    /**
+     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
+     * call is less optimal than calling updateStackTransforms directly.
+     */
+    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
+                                                            int stackScroll,
+                                                            int[] visibleRangeOut,
+                                                            boolean boundTranslationsToRect) {
+        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
+        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
+                boundTranslationsToRect);
         return taskTransforms;
     }
 
@@ -245,14 +292,13 @@
             int[] visibleRange = mTmpVisibleRange;
             int stackScroll = getStackScroll();
             ArrayList<Task> tasks = mStack.getTasks();
-            ArrayList<TaskViewTransform> taskTransforms = getStackTransforms(tasks, stackScroll,
-                    visibleRange, false);
+            updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false);
 
             // Update the visible state of all the tasks
             int taskCount = tasks.size();
             for (int i = 0; i < taskCount; i++) {
                 Task task = tasks.get(i);
-                TaskViewTransform transform = taskTransforms.get(i);
+                TaskViewTransform transform = mTaskTransforms.get(i);
                 TaskView tv = getChildViewForTask(task);
 
                 if (transform.visible) {
@@ -281,10 +327,10 @@
                 TaskView tv = (TaskView) getChildAt(i);
                 Task task = tv.getTask();
                 int taskIndex = mStack.indexOfTask(task);
-                if (taskIndex < 0 || !taskTransforms.get(taskIndex).visible) {
+                if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) {
                     mViewPool.returnViewToPool(tv);
                 } else {
-                    tv.updateViewPropertiesToTaskTransform(null, taskTransforms.get(taskIndex),
+                    tv.updateViewPropertiesToTaskTransform(null, mTaskTransforms.get(taskIndex),
                             mStackViewsAnimationDuration);
                 }
             }
@@ -361,7 +407,7 @@
         mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
         mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
                 curScroll, 250));
-        mScrollAnimator.setInterpolator(RecentsConfiguration.getInstance().fastOutSlowInInterpolator);
+        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
         mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
@@ -543,48 +589,31 @@
     /** Enables the hw layers and increments the hw layer requirement ref count */
     void addHwLayersRefCount(String reason) {
         if (Console.Enabled) {
+            int refCount = mHwLayersTrigger.getCount();
             Console.log(Constants.Log.UI.HwLayers,
                     "[TaskStackView|addHwLayersRefCount] refCount: " +
-                            mHwLayersRefCount + "->" + (mHwLayersRefCount + 1) + " " + reason);
+                            refCount + "->" + (refCount + 1) + " " + reason);
         }
-        if (mHwLayersRefCount == 0) {
-            // Enable hw layers on each of the children
-            int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                TaskView tv = (TaskView) getChildAt(i);
-                tv.enableHwLayers();
-            }
-        }
-        mHwLayersRefCount++;
+        mHwLayersTrigger.increment();
     }
 
     /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
         need them anymore. */
     void decHwLayersRefCount(String reason) {
         if (Console.Enabled) {
+            int refCount = mHwLayersTrigger.getCount();
             Console.log(Constants.Log.UI.HwLayers,
                     "[TaskStackView|decHwLayersRefCount] refCount: " +
-                            mHwLayersRefCount + "->" + (mHwLayersRefCount - 1) + " " + reason);
+                            refCount + "->" + (refCount - 1) + " " + reason);
         }
-        mHwLayersRefCount--;
-        if (mHwLayersRefCount == 0) {
-            // Disable hw layers on each of the children
-            int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                TaskView tv = (TaskView) getChildAt(i);
-                tv.disableHwLayers();
-            }
-        } else if (mHwLayersRefCount < 0) {
-            new Throwable("Invalid hw layers ref count").printStackTrace();
-            Console.logError(getContext(), "Invalid HW layers ref count");
-        }
+        mHwLayersTrigger.decrement();
     }
 
     @Override
     public void computeScroll() {
         if (mScroller.computeScrollOffset()) {
             setStackScroll(mScroller.getCurrY());
-            invalidate();
+            invalidate(mStackRect);
 
             // If we just finished scrolling, then disable the hw layers
             if (mScroller.isFinished()) {
@@ -616,7 +645,6 @@
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         if (Constants.DebugFlags.App.EnableTaskStackClipping) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
             TaskView tv = (TaskView) child;
             TaskView nextTv = null;
             TaskView tmpTv = null;
@@ -632,13 +660,13 @@
                 }
 
                 // Clip against the next view (if we aren't animating its alpha)
-                if (nextTv != null && nextTv.getAlpha() == 1f) {
+                if (nextTv != null) {
                     Rect curRect = tv.getClippingRect(mTmpRect);
                     Rect nextRect = nextTv.getClippingRect(mTmpRect2);
                     // The hit rects are relative to the task view, which needs to be offset by
                     // the system bar height
-                    curRect.offset(0, config.systemInsets.top);
-                    nextRect.offset(0, config.systemInsets.top);
+                    curRect.offset(0, mConfig.systemInsets.top);
+                    nextRect.offset(0, mConfig.systemInsets.top);
                     // Compute the clip region
                     Region clipRegion = new Region();
                     clipRegion.op(curRect, Region.Op.UNION);
@@ -660,7 +688,6 @@
         // 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
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
 
         // Compute the stack rects
         mRect.set(0, 0, width, height);
@@ -668,8 +695,8 @@
         mStackRect.left += insetLeft;
         mStackRect.bottom -= insetBottom;
 
-        int widthPadding = (int) (config.taskStackWidthPaddingPct * mStackRect.width());
-        int heightPadding = config.taskStackTopPaddingPx;
+        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
+        int heightPadding = mConfig.taskStackTopPaddingPx;
         if (Constants.DebugFlags.App.EnableSearchLayout) {
             mStackRect.top += heightPadding;
             mStackRect.left += widthPadding;
@@ -707,10 +734,9 @@
         }
 
         // Compute our stack/task rects
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         Rect taskStackBounds = new Rect();
-        config.getTaskStackBounds(width, height, taskStackBounds);
-        computeRects(width, height, taskStackBounds.left, config.systemInsets.bottom);
+        mConfig.getTaskStackBounds(width, height, taskStackBounds);
+        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
 
         // Debug logging
         if (Constants.Log.UI.MeasureAndLayout) {
@@ -768,47 +794,66 @@
         }
 
         if (mAwaitingFirstLayout) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
-
-            // Update the focused task index to be the next item to the top task
-            if (config.launchedFromAltTab) {
-                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
-            }
-
-            // Prepare the first view for its enter animation
-            if (config.launchedWithThumbnailAnimation) {
-                TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
-                if (tv != null) {
-                    tv.prepareAnimateOnEnterRecents();
-                }
-            }
-
             // Mark that we have completely the first layout
             mAwaitingFirstLayout = false;
 
+            // Prepare the first view for its enter animation
+            int offsetTopAlign = -mTaskRect.top;
+            int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top);
+            for (int i = childCount - 1; i >= 0; i--) {
+                TaskView tv = (TaskView) getChildAt(i);
+                tv.prepareAnimateEnterRecents((i == (getChildCount() - 1)), offsetTopAlign,
+                        offscreenY, mTaskRect);
+            }
+
             // If the enter animation started already and we haven't completed a layout yet, do the
             // enter animation now
             if (mStartEnterAnimationRequestedAfterLayout) {
-                startOnEnterAnimation();
+                startOnEnterAnimation(mStartEnterAnimationContext);
+                mStartEnterAnimationRequestedAfterLayout = false;
+                mStartEnterAnimationContext = null;
+            }
+
+            // Update the focused task index to be the next item to the top task
+            if (mConfig.launchedWithAltTab) {
+                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
             }
         }
     }
 
     /** Requests this task stacks to start it's enter-recents animation */
-    public void startOnEnterAnimation() {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (!config.launchedWithThumbnailAnimation) return;
-
+    public void startOnEnterAnimation(ViewAnimation.TaskViewEnterContext ctx) {
         // If we are still waiting to layout, then just defer until then
         if (mAwaitingFirstLayout) {
             mStartEnterAnimationRequestedAfterLayout = true;
+            mStartEnterAnimationContext = ctx;
             return;
         }
 
-        // Animate the task bar of the first task view
-        TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
-        if (tv != null) {
-            tv.animateOnEnterRecents();
+        // Animate all the task views into view
+        ctx.taskRect = mTaskRect;
+        ctx.stackRectSansPeek = mStackRectSansPeek;
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            TaskView tv = (TaskView) getChildAt(i);
+            TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()),
+                    getStackScroll());
+            ctx.stackViewIndex = i;
+            ctx.stackViewCount = childCount;
+            ctx.isFrontMost = (i == (getChildCount() - 1));
+            ctx.transform = transform;
+            tv.animateOnEnterRecents(ctx);
+        }
+    }
+
+    /** Requests this task stacks to start it's exit-recents animation. */
+    public void startOnExitAnimation(ViewAnimation.TaskViewExitContext ctx) {
+        // Animate all the task views into view
+        ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top);
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            tv.animateOnExitRecents(ctx);
         }
     }
 
@@ -871,8 +916,7 @@
                         ArrayList<TaskViewTransform> curTaskTransforms,
                         ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
-                        ArrayList<TaskView> childrenToRemoveOut,
-                        RecentsConfiguration config) {
+                        ArrayList<TaskView> childrenToRemoveOut) {
         // Animate all of the existing views out of view (if they are not in the visible range in
         // the new stack) or to their final positions in the new stack
         int movement = 0;
@@ -902,7 +946,7 @@
             childViewTransformsOut.put(tv, new Pair(0, toTransform));
         }
         return Utilities.calculateTranslationAnimationDuration(movement,
-                config.filteringCurrentViewsMinAnimDuration);
+                mConfig.filteringCurrentViewsMinAnimDuration);
     }
 
     /**
@@ -911,8 +955,7 @@
      */
     int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
                          ArrayList<TaskViewTransform> taskTransforms,
-                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
-                         RecentsConfiguration config) {
+                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut) {
         int offset = 0;
         int movement = 0;
         int taskCount = tasks.size();
@@ -942,7 +985,7 @@
             }
         }
         return Utilities.calculateTranslationAnimationDuration(movement,
-                config.filteringNewViewsMinAnimDuration);
+                mConfig.filteringNewViewsMinAnimDuration);
     }
 
     /** Orchestrates the animations of the current child views and any new views. */
@@ -950,22 +993,20 @@
                               ArrayList<TaskViewTransform> curTaskTransforms,
                               final ArrayList<Task> tasks,
                               final ArrayList<TaskViewTransform> taskTransforms) {
-        final RecentsConfiguration config = RecentsConfiguration.getInstance();
-
         // Calculate the transforms to animate out all the existing views if they are not in the
         // new visible range (or to their final positions in the stack if they are)
         final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
         final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms =
                 new HashMap<TaskView, Pair<Integer, TaskViewTransform>>();
         int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
-                taskTransforms, childViewTransforms, childrenToRemove, config);
+                taskTransforms, childViewTransforms, childrenToRemove);
 
         // If all the current views are in the visible range of the new stack, then don't wait for
         // views to animate out and animate all the new views into their place
         final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
         if (unifyNewViewAnimation) {
             int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
-                    childViewTransforms, config);
+                    childViewTransforms);
             duration = Math.max(duration, inDuration);
         }
 
@@ -989,7 +1030,7 @@
                                     // For views that are not already visible, animate them in
                                     childViewTransforms.clear();
                                     int duration = getEnterTransformsForFilterAnimation(tasks,
-                                            taskTransforms, childViewTransforms, config);
+                                            taskTransforms, childViewTransforms);
                                     for (final TaskView tv : childViewTransforms.keySet()) {
                                         Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
                                         tv.animate().setStartDelay(t.first);
@@ -1127,7 +1168,7 @@
         }
 
         // Enable hw layers on this view if hw layers are enabled on the stack
-        if (mHwLayersRefCount > 0) {
+        if (mHwLayersTrigger.getCount() > 0) {
             tv.enableHwLayers();
         }
     }
@@ -1196,7 +1237,6 @@
 
     @Override
     public void onComponentRemoved(Set<ComponentName> cns) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         // For other tasks, just remove them directly if they no longer exist
         ArrayList<Task> tasks = mStack.getTasks();
         for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -1502,7 +1542,7 @@
                             mSv.mMinScroll, mSv.mMaxScroll,
                             0, overscrollRange);
                     // Invalidate to kick off computeScroll
-                    mSv.invalidate();
+                    mSv.invalidate(mSv.mStackRect);
                 } else if (mSv.isScrollOutOfBounds()) {
                     // Animate the scroll back into bounds
                     // XXX: Make this animation a function of the velocity OR distance
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 5df5e4d..9e4386f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -28,9 +28,11 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
@@ -45,10 +47,10 @@
         public void onTaskAppInfoClicked(TaskView tv);
         public void onTaskFocused(TaskView tv);
         public void onTaskDismissed(TaskView tv);
-
-        // public void onTaskViewReboundToTask(TaskView tv, Task t);
     }
 
+    RecentsConfiguration mConfig;
+
     int mDim;
     int mMaxDim;
     TimeInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -59,11 +61,21 @@
     boolean mClipViewInStack;
     Point mLastTouchDown = new Point();
     Path mRoundedRectClipPath = new Path();
+    Rect mTmpRect = new Rect();
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
     TaskViewCallbacks mCb;
 
+    // Optimizations
+    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    updateDimOverlayFromScale();
+                }
+            };
+
 
     public TaskView(Context context) {
         this(context, null);
@@ -79,13 +91,13 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
     }
 
     @Override
     protected void onFinishInflate() {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        mMaxDim = config.taskStackMaxDim;
+        mMaxDim = mConfig.taskStackMaxDim;
 
         // By default, all views are clipped to other views in their stack
         mClipViewInStack = true;
@@ -104,8 +116,7 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
         // Update the rounded rect clip path
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        float radius = config.taskViewRoundedCornerRadiusPx;
+        float radius = mConfig.taskViewRoundedCornerRadiusPx;
         mRoundedRectClipPath.reset();
         mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
                 radius, radius, Path.Direction.CW);
@@ -113,7 +124,7 @@
         // Update the outline
         Outline o = new Outline();
         o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() -
-                config.taskViewShadowOutlineBottomInsetPx, radius);
+                mConfig.taskViewShadowOutlineBottomInsetPx, radius);
         setOutline(o);
     }
 
@@ -141,7 +152,10 @@
     /** Synchronizes this view's properties with the task's transform */
     void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
                                              TaskViewTransform toTransform, int duration) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]",
+                    "duration: " + duration, Console.AnsiPurple);
+        }
 
         // Update the bar view
         mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration);
@@ -164,15 +178,10 @@
                     .scaleX(toTransform.scale)
                     .scaleY(toTransform.scale)
                     .alpha(toTransform.alpha)
+                    .setStartDelay(0)
                     .setDuration(duration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
-                    .withLayer()
-                    .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                        @Override
-                        public void onAnimationUpdate(ValueAnimator animation) {
-                            updateDimOverlayFromScale();
-                        }
-                    })
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                    .setUpdateListener(mUpdateDimListener)
                     .start();
         } else {
             setTranslationY(toTransform.translationY);
@@ -197,6 +206,7 @@
         setScaleX(1f);
         setScaleY(1f);
         setAlpha(1f);
+        mDim = 0;
         invalidate();
     }
 
@@ -221,40 +231,103 @@
 
     /** 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 prepareAnimateOnEnterRecents() {
-        mBarView.setVisibility(View.INVISIBLE);
+    public void prepareAnimateEnterRecents(boolean isTaskViewFrontMost, int offsetY, int offscreenY,
+                                           Rect taskRect) {
+        if (mConfig.launchedFromAppWithScreenshot) {
+            if (isTaskViewFrontMost) {
+                // 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.prepareAnimateEnterRecents();
+            } else {
+                // Top align the task views
+                setTranslationY(offsetY);
+                setScaleX(1f);
+                setScaleY(1f);
+            }
+
+        } else if (mConfig.launchedFromAppWithThumbnail) {
+            if (isTaskViewFrontMost) {
+                // Hide the front most task bar view so we can animate it in
+                mBarView.prepareAnimateEnterRecents();
+            }
+
+        } else if (mConfig.launchedFromHome) {
+            // Move the task view off screen (below) so we can animate it in
+            setTranslationY(offscreenY);
+            setScaleX(1f);
+            setScaleY(1f);
+        }
     }
 
     /** Animates this task view as it enters recents */
-    public void animateOnEnterRecents() {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        mBarView.setVisibility(View.VISIBLE);
-        mBarView.setTranslationY(-mBarView.getMeasuredHeight());
-        mBarView.animate()
-                .translationY(0)
-                .setStartDelay(config.taskBarEnterAnimDelay)
-                .setInterpolator(config.fastOutSlowInInterpolator)
-                .setDuration(config.taskBarEnterAnimDuration)
+    public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx) {
+        TaskViewTransform transform = ctx.transform;
+
+        if (mConfig.launchedFromAppWithScreenshot) {
+            if (ctx.isFrontMost) {
+                // Animate the full screenshot down first, before swapping with this task view
+                ctx.fullScreenshot.animateOnEnterRecents(ctx, new Runnable() {
+                    @Override
+                    public void run() {
+                        // Animate the task bar of the first task view
+                        mBarView.animateOnEnterRecents(0);
+                        setVisibility(View.VISIBLE);
+                    }
+                });
+            } else {
+                // Animate the tasks down behind the full screenshot
+                animate()
+                        .scaleX(transform.scale)
+                        .scaleY(transform.scale)
+                        .translationY(transform.translationY)
+                        .setStartDelay(0)
+                        .setInterpolator(mConfig.linearOutSlowInInterpolator)
+                        .setDuration(475)
+                        .withLayer()
+                        .start();
+            }
+
+        } else if (mConfig.launchedFromAppWithThumbnail) {
+            if (ctx.isFrontMost) {
+                // Animate the task bar of the first task view
+                mBarView.animateOnEnterRecents(-1);
+            }
+
+        } else if (mConfig.launchedFromHome) {
+            // Animate the tasks up
+            int frontIndex = (ctx.stackViewCount - ctx.stackViewIndex - 1);
+            int delay = mConfig.taskBarEnterAnimDelay +
+                    frontIndex * mConfig.taskViewEnterFromHomeDelay;
+            animate()
+                    .scaleX(transform.scale)
+                    .scaleY(transform.scale)
+                    .translationY(transform.translationY)
+                    .setStartDelay(delay)
+                    .setInterpolator(mConfig.quintOutInterpolator)
+                    .setDuration(mConfig.taskViewEnterFromHomeDuration)
+                    .withLayer()
+                    .start();
+        }
+    }
+
+    /** Animates this task view as it leaves recents */
+    public void animateOnExitRecents(ViewAnimation.TaskViewExitContext ctx) {
+        animate()
+                .translationY(ctx.offscreenTranslationY)
+                .setStartDelay(0)
+                .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                .setDuration(mConfig.taskViewEnterFromHomeDuration)
                 .withLayer()
+                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
                 .start();
+        ctx.postAnimationTrigger.increment();
     }
 
     /** Animates this task view as it exits recents */
-    public void animateOnLeavingRecents(final Runnable r) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        mBarView.animate()
-            .translationY(-mBarView.getMeasuredHeight())
-            .setStartDelay(0)
-            .setInterpolator(config.fastOutLinearInInterpolator)
-            .setDuration(config.taskBarExitAnimDuration)
-            .withLayer()
-            .withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    post(r);
-                }
-            })
-            .start();
+    public void animateOnLaunchingTask(final Runnable r) {
+        mBarView.animateOnLaunchingTask(r);
     }
 
     /** Animates the deletion of this task view */
@@ -262,20 +335,24 @@
         // Disabling clipping with the stack while the view is animating away
         setClipViewInStack(false);
 
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        animate().translationX(config.taskViewRemoveAnimTranslationXPx)
+        animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
             .alpha(0f)
             .setStartDelay(0)
-            .setInterpolator(config.fastOutSlowInInterpolator)
-            .setDuration(config.taskViewRemoveAnimDuration)
+            .setInterpolator(mConfig.fastOutSlowInInterpolator)
+            .setDuration(mConfig.taskViewRemoveAnimDuration)
             .withLayer()
             .withEndAction(new Runnable() {
                 @Override
                 public void run() {
-                    post(r);
+                    // We just throw this into a runnable because starting a view property
+                    // animation using layers can cause inconsisten results if we try and
+                    // update the layers while the animation is running.  In some cases,
+                    // the runnabled passed in may start an animation which also uses layers
+                    // so we defer all this by posting this.
+                    r.run();
 
                     // Re-enable clipping with the stack (we will reuse this view)
-                    setClipViewInStack(false);
+                    setClipViewInStack(true);
                 }
             })
             .start();
@@ -293,11 +370,13 @@
     /** Enable the hw layers on this task view */
     void enableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        mBarView.enableHwLayers();
     }
 
     /** Disable the hw layers on this task view */
     void disableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
+        mBarView.disableHwLayers();
     }
 
     /**
@@ -305,7 +384,7 @@
      * view.
      */
     boolean shouldClipViewInStack() {
-        return mClipViewInStack;
+        return mClipViewInStack && (getVisibility() == View.VISIBLE);
     }
 
     /** Sets whether this view should be clipped, or clipped against. */
@@ -313,9 +392,8 @@
         if (clip != mClipViewInStack) {
             mClipViewInStack = clip;
             if (getParent() instanceof View) {
-                Rect r = new Rect();
-                getHitRect(r);
-                ((View) getParent()).invalidate(r);
+                getHitRect(mTmpRect);
+                ((View) getParent()).invalidate(mTmpRect);
             }
         }
     }
@@ -391,8 +469,7 @@
             mBarView.mApplicationIcon.setOnClickListener(this);
             mBarView.mDismissButton.setOnClickListener(this);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
-                RecentsConfiguration config = RecentsConfiguration.getInstance();
-                if (config.developerOptionsEnabled) {
+                if (mConfig.developerOptionsEnabled) {
                     mBarView.mApplicationIcon.setOnLongClickListener(this);
                 }
             }
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 3c3ebd7..4a76872 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -47,7 +47,8 @@
 
     @Override
     public String toString() {
-        return "TaskViewTransform y: " + translationY + " scale: " + scale + " alpha: " + alpha +
-                " visible: " + visible + " rect: " + rect + " dismissAlpha: " + dismissAlpha;
+        return "TaskViewTransform y: " + translationY + " z: " + translationZ + " scale: " + scale +
+                " alpha: " + alpha + " visible: " + visible + " rect: " + rect +
+                " dismissAlpha: " + dismissAlpha;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
new file mode 100644
index 0000000..b5e8ffd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -0,0 +1,60 @@
+/*
+ * 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.graphics.Rect;
+import com.android.systemui.recents.ReferenceCountedTrigger;
+
+/* Common code related to view animations */
+public class ViewAnimation {
+
+    /* The animation context for a task view animation into Recents */
+    public static class TaskViewEnterContext {
+        // The full screenshot view that we are animating down
+        FullScreenTransitionView fullScreenshot;
+        // The transform of the current task view
+        TaskViewTransform transform;
+        // The stack rect that the transform is relative to
+        Rect stackRectSansPeek;
+        // The task rect
+        Rect taskRect;
+        // The view index of the current task view
+        int stackViewIndex;
+        // The total number of task views
+        int stackViewCount;
+        // Whether this is the front most task view
+        boolean isFrontMost;
+
+        public TaskViewEnterContext(FullScreenTransitionView fss) {
+            fullScreenshot = fss;
+        }
+    }
+
+    /* The animation context for a task view animation out of Recents */
+    public static class TaskViewExitContext {
+        // 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 screen
+        int offscreenTranslationY;
+
+        public TaskViewExitContext(ReferenceCountedTrigger t) {
+            postAnimationTrigger = t;
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 4ed1888..a38d18c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1708,7 +1708,6 @@
         }
 
         if (mStatusBarWindow != null) {
-
             // release focus immediately to kick off focus change transition
             mStatusBarWindowManager.setStatusBarFocusable(false);
 
@@ -3134,4 +3133,30 @@
             }
         }
     };
+
+    // Recents
+
+    @Override
+    protected void showRecents(boolean triggeredFromAltTab) {
+        // Set the recents visibility flag
+        mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
+        notifyUiVisibilityChanged(mSystemUiVisibility);
+        super.showRecents(triggeredFromAltTab);
+    }
+
+    @Override
+    protected void hideRecents(boolean triggeredFromAltTab) {
+        // Unset the recents visibility flag
+        mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE;
+        notifyUiVisibilityChanged(mSystemUiVisibility);
+        super.hideRecents(triggeredFromAltTab);
+    }
+
+    @Override
+    protected void toggleRecents() {
+        // Toggle the recents visibility flag
+        mSystemUiVisibility ^= View.RECENT_APPS_VISIBLE;
+        notifyUiVisibilityChanged(mSystemUiVisibility);
+        super.toggleRecents();
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 03d29c0..cacf66b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -296,6 +296,7 @@
     WindowState mLastInputMethodWindow = null;
     WindowState mLastInputMethodTargetWindow = null;
 
+    boolean mRecentsVisible;
     int mRecentAppsHeldModifiers;
     boolean mLanguageSwitchKeyPressed;
 
@@ -2632,6 +2633,11 @@
                     }
                 }
             });
+        } else if (mRecentsVisible) {
+            // Recents is started on top of Home, so when we launch home while recents is open, let
+            // it do its own animation and then finish itself
+            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+            hideRecentApps(false);
         } else {
             // no keyguard stuff to worry about, just launch home!
             try {
@@ -2723,6 +2729,7 @@
     public int adjustSystemUiVisibilityLw(int visibility) {
         mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
         mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
+        mRecentsVisible = (visibility & View.RECENT_APPS_VISIBLE) > 0;
 
         // Reset any bits in mForceClearingStatusBarVisibility that
         // are now clear.