Moving alternate recents component logic into its own file.

- Adding min delay between toggles of recents
- Merging changes to show related recents

Change-Id: I2201a9f98417e26cdfb1c16cb0a2ef095d2cd5fc
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 56acf04..10b6d49 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -16,142 +16,54 @@
 
 package com.android.systemui.recent;
 
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.util.Log;
 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 com.android.systemui.RecentsComponent;
 import com.android.systemui.SystemUI;
-
-import java.util.List;
+import com.android.systemui.recents.AlternateRecentsComponent;
 
 
 public class Recents extends SystemUI implements RecentsComponent {
-    /** A handler for messages from the recents implementation */
-    class RecentsMessageHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            if (!mUseAlternateRecents) return;
-            if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) {
-                Resources res = mContext.getResources();
-                float statusBarHeight = res.getDimensionPixelSize(
-                        com.android.internal.R.dimen.status_bar_height);
-                mFirstTaskRect = (Rect) msg.getData().getParcelable("taskRect");
-                mFirstTaskRect.offset(0, (int) statusBarHeight);
-            }
-        }
-    }
-
-    /** A service connection to the recents implementation */
-    class RecentsServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (!mUseAlternateRecents) return;
-
-            Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceConnected] toggleRecents: " +
-                    mToggleRecentsUponServiceBound);
-            mService = new Messenger(service);
-            mServiceIsBound = true;
-
-            // Toggle recents if this service connection was triggered by hitting the recents button
-            if (mToggleRecentsUponServiceBound) {
-                startAlternateRecentsActivity();
-            }
-            mToggleRecentsUponServiceBound = false;
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            if (!mUseAlternateRecents) return;
-
-            Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceDisconnected]");
-            mService = null;
-            mServiceIsBound = false;
-        }
-    }
-
     private static final String TAG = "Recents";
     private static final boolean DEBUG = true;
 
-    final static int MSG_UPDATE_FOR_CONFIGURATION = 0;
-    final static int MSG_UPDATE_TASK_THUMBNAIL = 1;
-    final static int MSG_PRELOAD_TASKS = 2;
-    final static int MSG_CANCEL_PRELOAD_TASKS = 3;
-    final static int MSG_CLOSE_RECENTS = 4;
-    final static int MSG_TOGGLE_RECENTS = 5;
-
-    final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
-    final static String sRecentsPackage = "com.android.systemui";
-    final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
-    final static String sRecentsService = "com.android.systemui.recents.RecentsService";
-
     // Which recents to use
     boolean mUseAlternateRecents;
-
-    // Recents service binding
-    Messenger mService = null;
-    Messenger mMessenger;
-    boolean mServiceIsBound = false;
-    boolean mToggleRecentsUponServiceBound;
-    RecentsServiceConnection mConnection = new RecentsServiceConnection();
-
-    View mStatusBarView;
-    Rect mFirstTaskRect = new Rect();
-
-    public Recents() {
-        mMessenger = new Messenger(new RecentsMessageHandler());
-    }
+    AlternateRecentsComponent mAlternateRecents;
 
     @Override
     public void start() {
-        mUseAlternateRecents =
-                SystemProperties.getBoolean("persist.recents.use_alternate", false);
+        mUseAlternateRecents = SystemProperties.getBoolean("persist.recents.use_alternate", false);
+        if (mUseAlternateRecents) {
+            if (mAlternateRecents == null) {
+                mAlternateRecents = new AlternateRecentsComponent(mContext);
+            }
+            mAlternateRecents.onStart();
+        }
 
         putComponent(RecentsComponent.class, this);
-
-        if (mUseAlternateRecents) {
-            Log.d(TAG, "[RecentsComponent|start]");
-
-            // Try to create a long-running connection to the recents service
-            bindToRecentsService(false);
-        }
     }
 
     @Override
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
         if (mUseAlternateRecents) {
             // Launch the alternate recents if required
-            toggleAlternateRecents(display, layoutDirection, statusBarView);
+            mAlternateRecents.onToggleRecents(display, layoutDirection, statusBarView);
             return;
         }
 
@@ -298,222 +210,17 @@
         }
     }
 
-    /** Toggles the alternate recents activity */
-    public void toggleAlternateRecents(Display display, int layoutDirection, View statusBarView) {
-        if (!mUseAlternateRecents) return;
-
-        Log.d(TAG, "[RecentsComponent|toggleRecents] serviceIsBound: " + mServiceIsBound);
-        mStatusBarView = statusBarView;
-        if (!mServiceIsBound) {
-            // Try to create a long-running connection to the recents service before toggling
-            // recents
-            bindToRecentsService(true);
-            return;
-        }
-
-        try {
-            startAlternateRecentsActivity();
-        } catch (ActivityNotFoundException e) {
-            Log.e(TAG, "Failed to launch RecentAppsIntent", e);
-        }
-    }
-
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
-        if (mServiceIsBound) {
-            Resources res = mContext.getResources();
-            int statusBarHeight = res.getDimensionPixelSize(
-                    com.android.internal.R.dimen.status_bar_height);
-            int navBarHeight = res.getDimensionPixelSize(
-                    com.android.internal.R.dimen.navigation_bar_height);
-            Rect rect = new Rect();
-            WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-            wm.getDefaultDisplay().getRectSize(rect);
-
-            // Try and update the recents configuration
-            try {
-                Bundle data = new Bundle();
-                data.putParcelable("windowRect", rect);
-                data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0));
-                Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0);
-                msg.setData(data);
-                msg.replyTo = mMessenger;
-                mService.send(msg);
-            } catch (RemoteException re) {
-                re.printStackTrace();
-            }
-        }
-    }
-
-    /** Binds to the recents implementation */
-    private void bindToRecentsService(boolean toggleRecentsUponConnection) {
-        if (!mUseAlternateRecents) return;
-
-        mToggleRecentsUponServiceBound = toggleRecentsUponConnection;
-        Intent intent = new Intent();
-        intent.setClassName(sRecentsPackage, sRecentsService);
-        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
-    }
-
-    /** Loads the first task thumbnail */
-    Bitmap loadFirstTaskThumbnail() {
-        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
-                ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED,
-                UserHandle.CURRENT.getIdentifier());
-        for (ActivityManager.RecentTaskInfo t : tasks) {
-            // Skip tasks in the home stack
-            if (am.isInHomeStack(t.persistentId)) {
-                return null;
-            }
-
-            Bitmap thumbnail = am.getTaskTopThumbnail(t.persistentId);
-            return thumbnail;
-        }
-        return null;
-    }
-
-    /** Returns whether there is a first task */
-    boolean hasFirstTask() {
-        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
-                ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED,
-                UserHandle.CURRENT.getIdentifier());
-        for (ActivityManager.RecentTaskInfo t : tasks) {
-            // Skip tasks in the home stack
-            if (am.isInHomeStack(t.persistentId)) {
-                continue;
-            }
-
-            return true;
-        }
-        return false;
-    }
-
-    /** 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]);
-    }
-
-    /** Starts the recents activity */
-    void startAlternateRecentsActivity() {
-        // If Recents is the front most activity, then we should just communicate with it directly
-        // to launch the first task or dismiss itself
-        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
-        if (!tasks.isEmpty()) {
-            ComponentName topActivity = tasks.get(0).topActivity;
-            Log.d(TAG, "[RecentsComponent|topActivity] " + topActivity);
-
-            // Check if the front most activity is recents
-            if (topActivity.getPackageName().equals(sRecentsPackage) &&
-                    topActivity.getClassName().equals(sRecentsActivity)) {
-                // Notify Recents to toggle itself
-                try {
-                    Bundle data = new Bundle();
-                    Message msg = Message.obtain(null, MSG_TOGGLE_RECENTS, 0, 0);
-                    msg.setData(data);
-                    mService.send(msg);
-                } catch (RemoteException re) {
-                    re.printStackTrace();
-                }
-                return;
-            }
-        }
-
-        // XXX: If window transitions are currently happening, then we should eat up the event here
-
-        // Otherwise, Recents is not the front-most activity and we should animate into it
-        Rect taskRect = mFirstTaskRect;
-        if (taskRect != null && taskRect.width() > 0 && taskRect.height() > 0 && hasFirstTask()) {
-            // Loading from thumbnail
-            Bitmap thumbnail;
-            Bitmap firstThumbnail = loadFirstTaskThumbnail();
-            if (firstThumbnail == null) {
-                // Load the thumbnail from the screenshot
-                WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-                Display display = wm.getDefaultDisplay();
-                Bitmap screenshot = takeScreenshot(display);
-                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(mFirstTaskRect.width(), mFirstTaskRect.height(),
-                        Bitmap.Config.ARGB_8888);
-                Canvas c = new Canvas(thumbnail);
-                c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size),
-                        new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
-                c.setBitmap(null);
-                // Recycle the old screenshot
-                screenshot.recycle();
-            } else {
-                // Create the thumbnail
-                thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.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, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
-                c.setBitmap(null);
-                // Recycle the old thumbnail
-                firstThumbnail.recycle();
-            }
-
-            ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
-                    thumbnail, mFirstTaskRect.left, mFirstTaskRect.top, null);
-            startAlternateRecentsActivity(opts);
-        } else {
-            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
-                    R.anim.recents_from_launcher_enter,
-                    R.anim.recents_from_launcher_exit);
-            startAlternateRecentsActivity(opts);
-        }
-    }
-
-    /** Starts the recents activity */
-    void startAlternateRecentsActivity(ActivityOptions opts) {
-        Intent intent = new Intent(sToggleRecentsAction);
-        intent.setClassName(sRecentsPackage, sRecentsActivity);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        if (opts != null) {
-            mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
-                    UserHandle.USER_CURRENT));
-        } else {
-            mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+        if (mUseAlternateRecents) {
+            mAlternateRecents.onConfigurationChanged(newConfig);
         }
     }
 
     @Override
     public void preloadRecentTasksList() {
         if (mUseAlternateRecents) {
-            Log.d(TAG, "[RecentsComponent|preloadRecents]");
+            mAlternateRecents.onPreloadRecents();
         } else {
             Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
             intent.setClassName("com.android.systemui",
@@ -527,7 +234,7 @@
     @Override
     public void cancelPreloadingRecentTasksList() {
         if (mUseAlternateRecents) {
-            Log.d(TAG, "[RecentsComponent|cancelPreload]");
+            mAlternateRecents.onCancelPreloadingRecents();
         } else {
             Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
             intent.setClassName("com.android.systemui",
@@ -541,18 +248,7 @@
     @Override
     public void closeRecents() {
         if (mUseAlternateRecents) {
-            Log.d(TAG, "[RecentsComponent|closeRecents]");
-            if (mServiceIsBound) {
-                // Try and update the recents configuration
-                try {
-                    Bundle data = new Bundle();
-                    Message msg = Message.obtain(null, MSG_CLOSE_RECENTS, 0, 0);
-                    msg.setData(data);
-                    mService.send(msg);
-                } catch (RemoteException re) {
-                    re.printStackTrace();
-                }
-            }
+            mAlternateRecents.onCloseRecents();
         } else {
             Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
             intent.setPackage("com.android.systemui");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
new file mode 100644
index 0000000..19a6b09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+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 com.android.systemui.RecentsComponent;
+import com.android.systemui.SystemUI;
+
+import java.util.List;
+
+/** A proxy implementation for the recents component */
+public class AlternateRecentsComponent {
+
+    /** A handler for messages from the recents implementation */
+    class RecentsMessageHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) {
+                Resources res = mContext.getResources();
+                float statusBarHeight = res.getDimensionPixelSize(
+                        com.android.internal.R.dimen.status_bar_height);
+                mFirstTaskRect = (Rect) msg.getData().getParcelable("taskRect");
+                mFirstTaskRect.offset(0, (int) statusBarHeight);
+            }
+        }
+    }
+
+    /** A service connection to the recents implementation */
+    class RecentsServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Console.log(Constants.DebugFlags.App.RecentsComponent,
+                    "[RecentsComponent|ServiceConnection|onServiceConnected]",
+                    "toggleRecents: " + mToggleRecentsUponServiceBound);
+            mService = new Messenger(service);
+            mServiceIsBound = true;
+
+            // Toggle recents if this service connection was triggered by hitting the recents button
+            if (mToggleRecentsUponServiceBound) {
+                startAlternateRecentsActivity();
+            }
+            mToggleRecentsUponServiceBound = false;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            Console.log(Constants.DebugFlags.App.RecentsComponent,
+                    "[RecentsComponent|ServiceConnection|onServiceDisconnected]");
+            mService = null;
+            mServiceIsBound = false;
+        }
+    }
+
+    final static int MSG_UPDATE_FOR_CONFIGURATION = 0;
+    final static int MSG_UPDATE_TASK_THUMBNAIL = 1;
+    final static int MSG_PRELOAD_TASKS = 2;
+    final static int MSG_CANCEL_PRELOAD_TASKS = 3;
+    final static int MSG_CLOSE_RECENTS = 4;
+    final static int MSG_TOGGLE_RECENTS = 5;
+
+    final static int sMinToggleDelay = 475;
+
+    final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
+    final static String sRecentsPackage = "com.android.systemui";
+    final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
+    final static String sRecentsService = "com.android.systemui.recents.RecentsService";
+
+    Context mContext;
+
+    // Recents service binding
+    Messenger mService = null;
+    Messenger mMessenger;
+    boolean mServiceIsBound = false;
+    boolean mToggleRecentsUponServiceBound;
+    RecentsServiceConnection mConnection = new RecentsServiceConnection();
+
+    View mStatusBarView;
+    Rect mFirstTaskRect = new Rect();
+    long mLastToggleTime;
+
+    public AlternateRecentsComponent(Context context) {
+        mContext = context;
+        mMessenger = new Messenger(new RecentsMessageHandler());
+    }
+
+    public void onStart() {
+        Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|start]");
+
+        // Try to create a long-running connection to the recents service
+        bindToRecentsService(false);
+    }
+
+    /** Toggles the alternate recents activity */
+    public void onToggleRecents(Display display, int layoutDirection, View statusBarView) {
+        Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|toggleRecents]",
+                "serviceIsBound: " + mServiceIsBound);
+        mStatusBarView = statusBarView;
+        if (!mServiceIsBound) {
+            // Try to create a long-running connection to the recents service before toggling
+            // recents
+            bindToRecentsService(true);
+            return;
+        }
+
+        try {
+            startAlternateRecentsActivity();
+        } catch (ActivityNotFoundException e) {
+            Console.logRawError("Failed to launch RecentAppsIntent", e);
+        }
+    }
+
+    public void onPreloadRecents() {
+        // Do nothing
+    }
+
+    public void onCancelPreloadingRecents() {
+        // Do nothing
+    }
+
+    public void onCloseRecents() {
+        Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|closeRecents]");
+        if (mServiceIsBound) {
+            // Try and update the recents configuration
+            try {
+                Bundle data = new Bundle();
+                Message msg = Message.obtain(null, MSG_CLOSE_RECENTS, 0, 0);
+                msg.setData(data);
+                mService.send(msg);
+            } catch (RemoteException re) {
+                re.printStackTrace();
+            }
+        }
+    }
+
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (mServiceIsBound) {
+            Resources res = mContext.getResources();
+            int statusBarHeight = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_height);
+            int navBarHeight = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.navigation_bar_height);
+            Rect rect = new Rect();
+            WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+            wm.getDefaultDisplay().getRectSize(rect);
+
+            // Try and update the recents configuration
+            try {
+                Bundle data = new Bundle();
+                data.putParcelable("windowRect", rect);
+                data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0));
+                Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0);
+                msg.setData(data);
+                msg.replyTo = mMessenger;
+                mService.send(msg);
+            } catch (RemoteException re) {
+                re.printStackTrace();
+            }
+        }
+    }
+
+    /** Binds to the recents implementation */
+    private void bindToRecentsService(boolean toggleRecentsUponConnection) {
+        mToggleRecentsUponServiceBound = toggleRecentsUponConnection;
+        Intent intent = new Intent();
+        intent.setClassName(sRecentsPackage, sRecentsService);
+        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    /** Loads the first task thumbnail */
+    Bitmap loadFirstTaskThumbnail() {
+        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
+                ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED,
+                UserHandle.CURRENT.getIdentifier());
+        for (ActivityManager.RecentTaskInfo t : tasks) {
+            // Skip tasks in the home stack
+            if (am.isInHomeStack(t.persistentId)) {
+                return null;
+            }
+
+            Bitmap thumbnail = am.getTaskTopThumbnail(t.persistentId);
+            return thumbnail;
+        }
+        return null;
+    }
+
+    /** Returns whether there is a first task */
+    boolean hasFirstTask() {
+        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
+                ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED,
+                UserHandle.CURRENT.getIdentifier());
+        for (ActivityManager.RecentTaskInfo t : tasks) {
+            // Skip tasks in the home stack
+            if (am.isInHomeStack(t.persistentId)) {
+                continue;
+            }
+
+            return true;
+        }
+        return false;
+    }
+
+    /** 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]);
+    }
+
+    /** Starts the recents activity */
+    void startAlternateRecentsActivity() {
+        // If the user has toggled it too quickly, then just eat up the event here (it's better than
+        // showing a janky screenshot).
+        // NOTE: Ideally, the screenshot mechanism would take the window transform into account
+        if (System.currentTimeMillis() - mLastToggleTime < sMinToggleDelay) {
+            return;
+        }
+
+        // If Recents is the front most activity, then we should just communicate with it directly
+        // to launch the first task or dismiss itself
+        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
+        if (!tasks.isEmpty()) {
+            ComponentName topActivity = tasks.get(0).topActivity;
+
+            // Check if the front most activity is recents
+            if (topActivity.getPackageName().equals(sRecentsPackage) &&
+                    topActivity.getClassName().equals(sRecentsActivity)) {
+                // Notify Recents to toggle itself
+                try {
+                    Bundle data = new Bundle();
+                    Message msg = Message.obtain(null, MSG_TOGGLE_RECENTS, 0, 0);
+                    msg.setData(data);
+                    mService.send(msg);
+                } catch (RemoteException re) {
+                    re.printStackTrace();
+                }
+                mLastToggleTime = System.currentTimeMillis();
+                return;
+            }
+        }
+
+        // Otherwise, Recents is not the front-most activity and we should animate into it
+        Rect taskRect = mFirstTaskRect;
+        if (taskRect != null && taskRect.width() > 0 && taskRect.height() > 0 && hasFirstTask()) {
+            // Loading from thumbnail
+            Bitmap thumbnail;
+            Bitmap firstThumbnail = loadFirstTaskThumbnail();
+            if (firstThumbnail == null) {
+                // Load the thumbnail from the screenshot
+                WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+                Display display = wm.getDefaultDisplay();
+                Bitmap screenshot = takeScreenshot(display);
+                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(mFirstTaskRect.width(), mFirstTaskRect.height(),
+                        Bitmap.Config.ARGB_8888);
+                Canvas c = new Canvas(thumbnail);
+                c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size),
+                        new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
+                c.setBitmap(null);
+                // Recycle the old screenshot
+                screenshot.recycle();
+            } else {
+                // Create the thumbnail
+                thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.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, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
+                c.setBitmap(null);
+                // Recycle the old thumbnail
+                firstThumbnail.recycle();
+            }
+
+            ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
+                    thumbnail, mFirstTaskRect.left, mFirstTaskRect.top, null);
+            startAlternateRecentsActivity(opts);
+        } else {
+            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
+                    R.anim.recents_from_launcher_enter,
+                    R.anim.recents_from_launcher_exit);
+            startAlternateRecentsActivity(opts);
+        }
+        mLastToggleTime = System.currentTimeMillis();
+    }
+
+    /** Starts the recents activity */
+    void startAlternateRecentsActivity(ActivityOptions opts) {
+        Intent intent = new Intent(sToggleRecentsAction);
+        intent.setClassName(sRecentsPackage, sRecentsActivity);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        if (opts != null) {
+            mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
+                    UserHandle.USER_CURRENT));
+        } else {
+            mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Console.java b/packages/SystemUI/src/com/android/systemui/recents/Console.java
index db95193..614c4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Console.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Console.java
@@ -67,6 +67,11 @@
         Log.e("Recents", msg);
     }
 
+    /** Logs a raw error */
+    public static void logRawError(String msg, Exception e) {
+        Log.e("Recents", msg, e);
+    }
+
     /** Logs a divider bar */
     public static void logDivider(boolean condition) {
         if (condition) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index ede4ea8..59a591a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -32,6 +32,7 @@
             // This disables the bitmap and icon caches to
             public static final boolean DisableBackgroundCache = false;
 
+            public static final boolean RecentsComponent = false;
             public static final boolean TaskDataLoader = false;
             public static final boolean SystemUIHandshake = false;
             public static final boolean TimeSystemCalls = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index 5f9162d..c5e325e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -340,7 +340,8 @@
 
             // Get the recent tasks
             List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(25,
-                    ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
+                    ActivityManager.RECENT_IGNORE_UNAVAILABLE |
+                    ActivityManager.RECENT_INCLUDE_RELATED, UserHandle.CURRENT.getIdentifier());
             Collections.reverse(tasks);
             Console.log(Constants.DebugFlags.App.TimeSystemCalls,
                     "[RecentsTaskLoader|getRecentTasks]",